2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GList *compose_list = NULL;
189 static GSList *extra_headers = NULL;
191 static Compose *compose_generic_new (PrefsAccount *account,
195 GList *listAddress );
197 static Compose *compose_create (PrefsAccount *account,
202 static void compose_entry_mark_default_to (Compose *compose,
203 const gchar *address);
204 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
205 ComposeQuoteMode quote_mode,
209 static Compose *compose_forward_multiple (PrefsAccount *account,
210 GSList *msginfo_list);
211 static Compose *compose_reply (MsgInfo *msginfo,
212 ComposeQuoteMode quote_mode,
217 static Compose *compose_reply_mode (ComposeMode mode,
218 GSList *msginfo_list,
220 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
221 static void compose_update_privacy_systems_menu(Compose *compose);
223 static GtkWidget *compose_account_option_menu_create
225 static void compose_set_out_encoding (Compose *compose);
226 static void compose_set_template_menu (Compose *compose);
227 static void compose_destroy (Compose *compose);
229 static MailField compose_entries_set (Compose *compose,
231 ComposeEntryType to_type);
232 static gint compose_parse_header (Compose *compose,
234 static gint compose_parse_manual_headers (Compose *compose,
236 HeaderEntry *entries);
237 static gchar *compose_parse_references (const gchar *ref,
240 static gchar *compose_quote_fmt (Compose *compose,
246 gboolean need_unescape,
247 const gchar *err_msg);
249 static void compose_reply_set_entry (Compose *compose,
255 followup_and_reply_to);
256 static void compose_reedit_set_entry (Compose *compose,
259 static void compose_insert_sig (Compose *compose,
261 static ComposeInsertResult compose_insert_file (Compose *compose,
264 static gboolean compose_attach_append (Compose *compose,
267 const gchar *content_type,
268 const gchar *charset);
269 static void compose_attach_parts (Compose *compose,
272 static gboolean compose_beautify_paragraph (Compose *compose,
273 GtkTextIter *par_iter,
275 static void compose_wrap_all (Compose *compose);
276 static void compose_wrap_all_full (Compose *compose,
279 static void compose_set_title (Compose *compose);
280 static void compose_select_account (Compose *compose,
281 PrefsAccount *account,
284 static PrefsAccount *compose_current_mail_account(void);
285 /* static gint compose_send (Compose *compose); */
286 static gboolean compose_check_for_valid_recipient
288 static gboolean compose_check_entries (Compose *compose,
289 gboolean check_everything);
290 static gint compose_write_to_file (Compose *compose,
293 gboolean attach_parts);
294 static gint compose_write_body_to_file (Compose *compose,
296 static gint compose_remove_reedit_target (Compose *compose,
298 static void compose_remove_draft (Compose *compose);
299 static gint compose_queue_sub (Compose *compose,
303 gboolean check_subject,
304 gboolean remove_reedit_target);
305 static int compose_add_attachments (Compose *compose,
307 static gchar *compose_get_header (Compose *compose);
308 static gchar *compose_get_manual_headers_info (Compose *compose);
310 static void compose_convert_header (Compose *compose,
315 gboolean addr_field);
317 static void compose_attach_info_free (AttachInfo *ainfo);
318 static void compose_attach_remove_selected (GtkAction *action,
321 static void compose_template_apply (Compose *compose,
324 static void compose_attach_property (GtkAction *action,
326 static void compose_attach_property_create (gboolean *cancelled);
327 static void attach_property_ok (GtkWidget *widget,
328 gboolean *cancelled);
329 static void attach_property_cancel (GtkWidget *widget,
330 gboolean *cancelled);
331 static gint attach_property_delete_event (GtkWidget *widget,
333 gboolean *cancelled);
334 static gboolean attach_property_key_pressed (GtkWidget *widget,
336 gboolean *cancelled);
338 static void compose_exec_ext_editor (Compose *compose);
340 static gint compose_exec_ext_editor_real (const gchar *file);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
345 static void compose_set_ext_editor_sensitive (Compose *compose,
347 #endif /* G_OS_UNIX */
349 static void compose_undo_state_changed (UndoMain *undostruct,
354 static void compose_create_header_entry (Compose *compose);
355 static void compose_add_header_entry (Compose *compose, const gchar *header,
356 gchar *text, ComposePrefType pref_type);
357 static void compose_remove_header_entries(Compose *compose);
359 static void compose_update_priority_menu_item(Compose * compose);
361 static void compose_spell_menu_changed (void *data);
362 static void compose_dict_changed (void *data);
364 static void compose_add_field_list ( Compose *compose,
365 GList *listAddress );
367 /* callback functions */
369 static void compose_notebook_size_alloc (GtkNotebook *notebook,
370 GtkAllocation *allocation,
372 static gboolean compose_edit_size_alloc (GtkEditable *widget,
373 GtkAllocation *allocation,
374 GtkSHRuler *shruler);
375 static void account_activated (GtkComboBox *optmenu,
377 static void attach_selected (GtkTreeView *tree_view,
378 GtkTreePath *tree_path,
379 GtkTreeViewColumn *column,
381 static gboolean attach_button_pressed (GtkWidget *widget,
382 GdkEventButton *event,
384 static gboolean attach_key_pressed (GtkWidget *widget,
387 static void compose_send_cb (GtkAction *action, gpointer data);
388 static void compose_send_later_cb (GtkAction *action, gpointer data);
390 static void compose_save_cb (GtkAction *action,
393 static void compose_attach_cb (GtkAction *action,
395 static void compose_insert_file_cb (GtkAction *action,
397 static void compose_insert_sig_cb (GtkAction *action,
399 static void compose_replace_sig_cb (GtkAction *action,
402 static void compose_close_cb (GtkAction *action,
404 static void compose_print_cb (GtkAction *action,
407 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
409 static void compose_address_cb (GtkAction *action,
411 static void about_show_cb (GtkAction *action,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (GtkAction *action,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (GtkAction *action,
425 static void compose_redo_cb (GtkAction *action,
427 static void compose_cut_cb (GtkAction *action,
429 static void compose_copy_cb (GtkAction *action,
431 static void compose_paste_cb (GtkAction *action,
433 static void compose_paste_as_quote_cb (GtkAction *action,
435 static void compose_paste_no_wrap_cb (GtkAction *action,
437 static void compose_paste_wrap_cb (GtkAction *action,
439 static void compose_allsel_cb (GtkAction *action,
442 static void compose_advanced_action_cb (GtkAction *action,
445 static void compose_grab_focus_cb (GtkWidget *widget,
448 static void compose_changed_cb (GtkTextBuffer *textbuf,
451 static void compose_wrap_cb (GtkAction *action,
453 static void compose_wrap_all_cb (GtkAction *action,
455 static void compose_find_cb (GtkAction *action,
457 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
459 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
462 static void compose_toggle_ruler_cb (GtkToggleAction *action,
464 static void compose_toggle_sign_cb (GtkToggleAction *action,
466 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
468 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
469 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
470 static void activate_privacy_system (Compose *compose,
471 PrefsAccount *account,
473 static void compose_use_signing(Compose *compose, gboolean use_signing);
474 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
475 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
477 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
479 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
481 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
483 static void compose_attach_drag_received_cb (GtkWidget *widget,
484 GdkDragContext *drag_context,
487 GtkSelectionData *data,
491 static void compose_insert_drag_received_cb (GtkWidget *widget,
492 GdkDragContext *drag_context,
495 GtkSelectionData *data,
499 static void compose_header_drag_received_cb (GtkWidget *widget,
500 GdkDragContext *drag_context,
503 GtkSelectionData *data,
508 static gboolean compose_drag_drop (GtkWidget *widget,
509 GdkDragContext *drag_context,
511 guint time, gpointer user_data);
512 static gboolean completion_set_focus_to_subject
517 static void text_inserted (GtkTextBuffer *buffer,
522 static Compose *compose_generic_reply(MsgInfo *msginfo,
523 ComposeQuoteMode quote_mode,
527 gboolean followup_and_reply_to,
530 static void compose_headerentry_changed_cb (GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
534 ComposeHeaderEntry *headerentry);
535 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
536 ComposeHeaderEntry *headerentry);
538 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
540 static void compose_allow_user_actions (Compose *compose, gboolean allow);
542 static void compose_nothing_cb (GtkAction *action, gpointer data)
548 static void compose_check_all (GtkAction *action, gpointer data);
549 static void compose_highlight_all (GtkAction *action, gpointer data);
550 static void compose_check_backwards (GtkAction *action, gpointer data);
551 static void compose_check_forwards_go (GtkAction *action, gpointer data);
554 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
556 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
559 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
560 FolderItem *folder_item);
562 static void compose_attach_update_label(Compose *compose);
563 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
564 gboolean respect_default_to);
565 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
583 {"Spelling", NULL, N_("_Spelling") },
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
622 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
626 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
627 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
628 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
629 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
630 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 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_show(notebook);
7662 /* header labels and entries */
7663 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7664 compose_create_header(compose),
7665 gtk_label_new_with_mnemonic(_("Hea_der")));
7666 /* attachment list */
7667 attach_hbox = gtk_hbox_new(FALSE, 0);
7668 gtk_widget_show(attach_hbox);
7670 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7671 gtk_widget_show(attach_lab1);
7672 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7674 attach_lab2 = gtk_label_new("");
7675 gtk_widget_show(attach_lab2);
7676 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7678 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7679 compose_create_attach(compose),
7682 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7683 compose_create_others(compose),
7684 gtk_label_new_with_mnemonic(_("Othe_rs")));
7687 subject_hbox = gtk_hbox_new(FALSE, 0);
7688 gtk_widget_show(subject_hbox);
7690 subject_frame = gtk_frame_new(NULL);
7691 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7692 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7693 gtk_widget_show(subject_frame);
7695 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7696 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7697 gtk_widget_show(subject);
7699 label = gtk_label_new_with_mnemonic(_("Subject:"));
7700 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7701 gtk_widget_show(label);
7704 subject_entry = claws_spell_entry_new();
7706 subject_entry = gtk_entry_new();
7708 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7709 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7710 G_CALLBACK(compose_grab_focus_cb), compose);
7711 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
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_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7789 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7790 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7791 gtk_widget_show_all(paned);
7794 if (prefs_common.textfont) {
7795 PangoFontDescription *font_desc;
7797 font_desc = pango_font_description_from_string
7798 (prefs_common.textfont);
7800 gtk_widget_modify_font(text, font_desc);
7801 pango_font_description_free(font_desc);
7805 gtk_action_group_add_actions(action_group, compose_popup_entries,
7806 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7807 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7808 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7809 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7810 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7811 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7812 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7814 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7816 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7817 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7818 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7820 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7822 undostruct = undo_init(text);
7823 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7826 address_completion_start(window);
7828 compose->window = window;
7829 compose->vbox = vbox;
7830 compose->menubar = menubar;
7831 compose->handlebox = handlebox;
7833 compose->vbox2 = vbox2;
7835 compose->paned = paned;
7837 compose->attach_label = attach_lab2;
7839 compose->notebook = notebook;
7840 compose->edit_vbox = edit_vbox;
7841 compose->ruler_hbox = ruler_hbox;
7842 compose->ruler = ruler;
7843 compose->scrolledwin = scrolledwin;
7844 compose->text = text;
7846 compose->focused_editable = NULL;
7848 compose->popupmenu = popupmenu;
7850 compose->tmpl_menu = tmpl_menu;
7852 compose->mode = mode;
7853 compose->rmode = mode;
7855 compose->targetinfo = NULL;
7856 compose->replyinfo = NULL;
7857 compose->fwdinfo = NULL;
7859 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7860 g_str_equal, (GDestroyNotify) g_free, NULL);
7862 compose->replyto = NULL;
7864 compose->bcc = NULL;
7865 compose->followup_to = NULL;
7867 compose->ml_post = NULL;
7869 compose->inreplyto = NULL;
7870 compose->references = NULL;
7871 compose->msgid = NULL;
7872 compose->boundary = NULL;
7874 compose->autowrap = prefs_common.autowrap;
7875 compose->autoindent = prefs_common.auto_indent;
7876 compose->use_signing = FALSE;
7877 compose->use_encryption = FALSE;
7878 compose->privacy_system = NULL;
7880 compose->modified = FALSE;
7882 compose->return_receipt = FALSE;
7884 compose->to_list = NULL;
7885 compose->newsgroup_list = NULL;
7887 compose->undostruct = undostruct;
7889 compose->sig_str = NULL;
7891 compose->exteditor_file = NULL;
7892 compose->exteditor_pid = -1;
7893 compose->exteditor_tag = -1;
7894 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7897 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7898 if (mode != COMPOSE_REDIRECT) {
7899 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7900 strcmp(prefs_common.dictionary, "")) {
7901 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7902 prefs_common.alt_dictionary,
7903 conv_get_locale_charset_str(),
7904 prefs_common.misspelled_col,
7905 prefs_common.check_while_typing,
7906 prefs_common.recheck_when_changing_dict,
7907 prefs_common.use_alternate,
7908 prefs_common.use_both_dicts,
7909 GTK_TEXT_VIEW(text),
7910 GTK_WINDOW(compose->window),
7911 compose_dict_changed,
7912 compose_spell_menu_changed,
7915 alertpanel_error(_("Spell checker could not "
7917 gtkaspell_checkers_strerror());
7918 gtkaspell_checkers_reset_error();
7920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7924 compose->gtkaspell = gtkaspell;
7925 compose_spell_menu_changed(compose);
7926 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7929 compose_select_account(compose, account, TRUE);
7931 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7932 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7934 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7935 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7937 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7938 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7940 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7941 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7943 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7944 if (account->protocol != A_NNTP)
7945 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7946 prefs_common_translated_header_name("To:"));
7948 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7949 prefs_common_translated_header_name("Newsgroups:"));
7951 #ifndef USE_NEW_ADDRBOOK
7952 addressbook_set_target_compose(compose);
7954 if (mode != COMPOSE_REDIRECT)
7955 compose_set_template_menu(compose);
7957 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7960 compose_list = g_list_append(compose_list, compose);
7962 if (!prefs_common.show_ruler)
7963 gtk_widget_hide(ruler_hbox);
7965 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7968 compose->priority = PRIORITY_NORMAL;
7969 compose_update_priority_menu_item(compose);
7971 compose_set_out_encoding(compose);
7974 compose_update_actions_menu(compose);
7976 /* Privacy Systems menu */
7977 compose_update_privacy_systems_menu(compose);
7979 activate_privacy_system(compose, account, TRUE);
7980 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7982 gtk_widget_realize(window);
7984 gtk_widget_show(window);
7990 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7995 GtkWidget *optmenubox;
7998 GtkWidget *from_name = NULL;
7999 #if !(GTK_CHECK_VERSION(2,12,0))
8000 GtkTooltips *tips = compose->tooltips;
8003 gint num = 0, def_menu = 0;
8005 accounts = account_get_list();
8006 cm_return_val_if_fail(accounts != NULL, NULL);
8008 optmenubox = gtk_event_box_new();
8009 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8010 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8012 hbox = gtk_hbox_new(FALSE, 6);
8013 from_name = gtk_entry_new();
8015 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8016 G_CALLBACK(compose_grab_focus_cb), compose);
8018 for (; accounts != NULL; accounts = accounts->next, num++) {
8019 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8020 gchar *name, *from = NULL;
8022 if (ac == compose->account) def_menu = num;
8024 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8027 if (ac == compose->account) {
8028 if (ac->name && *ac->name) {
8030 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8031 from = g_strdup_printf("%s <%s>",
8033 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8035 from = g_strdup_printf("%s",
8037 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8040 COMBOBOX_ADD(menu, name, ac->account_id);
8045 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8047 g_signal_connect(G_OBJECT(optmenu), "changed",
8048 G_CALLBACK(account_activated),
8050 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8051 G_CALLBACK(compose_entry_popup_extend),
8054 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8055 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8057 CLAWS_SET_TIP(optmenubox,
8058 _("Account to use for this email"));
8059 CLAWS_SET_TIP(from_name,
8060 _("Sender address to be used"));
8062 compose->account_combo = optmenu;
8063 compose->from_name = from_name;
8068 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8070 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8071 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8072 Compose *compose = (Compose *) data;
8074 compose->priority = value;
8078 static void compose_reply_change_mode(Compose *compose,
8081 gboolean was_modified = compose->modified;
8083 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8085 cm_return_if_fail(compose->replyinfo != NULL);
8087 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8089 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8091 if (action == COMPOSE_REPLY_TO_ALL)
8093 if (action == COMPOSE_REPLY_TO_SENDER)
8095 if (action == COMPOSE_REPLY_TO_LIST)
8098 compose_remove_header_entries(compose);
8099 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8100 if (compose->account->set_autocc && compose->account->auto_cc)
8101 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8103 if (compose->account->set_autobcc && compose->account->auto_bcc)
8104 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8106 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8107 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8108 compose_show_first_last_header(compose, TRUE);
8109 compose->modified = was_modified;
8110 compose_set_title(compose);
8113 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8115 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8116 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8117 Compose *compose = (Compose *) data;
8120 compose_reply_change_mode(compose, value);
8123 static void compose_update_priority_menu_item(Compose * compose)
8125 GtkWidget *menuitem = NULL;
8126 switch (compose->priority) {
8127 case PRIORITY_HIGHEST:
8128 menuitem = gtk_ui_manager_get_widget
8129 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8132 menuitem = gtk_ui_manager_get_widget
8133 (compose->ui_manager, "/Menu/Options/Priority/High");
8135 case PRIORITY_NORMAL:
8136 menuitem = gtk_ui_manager_get_widget
8137 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8140 menuitem = gtk_ui_manager_get_widget
8141 (compose->ui_manager, "/Menu/Options/Priority/Low");
8143 case PRIORITY_LOWEST:
8144 menuitem = gtk_ui_manager_get_widget
8145 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8148 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8151 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8153 Compose *compose = (Compose *) data;
8155 gboolean can_sign = FALSE, can_encrypt = FALSE;
8157 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8159 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8162 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8163 g_free(compose->privacy_system);
8164 compose->privacy_system = NULL;
8165 if (systemid != NULL) {
8166 compose->privacy_system = g_strdup(systemid);
8168 can_sign = privacy_system_can_sign(systemid);
8169 can_encrypt = privacy_system_can_encrypt(systemid);
8172 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8174 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8175 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8178 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8180 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8181 GtkWidget *menuitem = NULL;
8182 GList *children, *amenu;
8183 gboolean can_sign = FALSE, can_encrypt = FALSE;
8184 gboolean found = FALSE;
8186 if (compose->privacy_system != NULL) {
8188 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8189 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8190 cm_return_if_fail(menuitem != NULL);
8192 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8195 while (amenu != NULL) {
8196 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8197 if (systemid != NULL) {
8198 if (strcmp(systemid, compose->privacy_system) == 0 &&
8199 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8200 menuitem = GTK_WIDGET(amenu->data);
8202 can_sign = privacy_system_can_sign(systemid);
8203 can_encrypt = privacy_system_can_encrypt(systemid);
8207 } else if (strlen(compose->privacy_system) == 0 &&
8208 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8209 menuitem = GTK_WIDGET(amenu->data);
8212 can_encrypt = FALSE;
8217 amenu = amenu->next;
8219 g_list_free(children);
8220 if (menuitem != NULL)
8221 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8223 if (warn && !found && strlen(compose->privacy_system)) {
8224 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8225 "will not be able to sign or encrypt this message."),
8226 compose->privacy_system);
8230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8234 static void compose_set_out_encoding(Compose *compose)
8236 CharSet out_encoding;
8237 const gchar *branch = NULL;
8238 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8240 switch(out_encoding) {
8241 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8242 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8243 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8244 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8245 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8246 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8247 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8248 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8249 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8250 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8251 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8252 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8253 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8254 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8255 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8256 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8257 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8258 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8259 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8260 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8261 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8262 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8263 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8264 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8265 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8266 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8267 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8268 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8269 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8270 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8271 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8272 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8273 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8275 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8278 static void compose_set_template_menu(Compose *compose)
8280 GSList *tmpl_list, *cur;
8284 tmpl_list = template_get_config();
8286 menu = gtk_menu_new();
8288 gtk_menu_set_accel_group (GTK_MENU (menu),
8289 gtk_ui_manager_get_accel_group(compose->ui_manager));
8290 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8291 Template *tmpl = (Template *)cur->data;
8292 gchar *accel_path = NULL;
8293 item = gtk_menu_item_new_with_label(tmpl->name);
8294 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8295 g_signal_connect(G_OBJECT(item), "activate",
8296 G_CALLBACK(compose_template_activate_cb),
8298 g_object_set_data(G_OBJECT(item), "template", tmpl);
8299 gtk_widget_show(item);
8300 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8301 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8305 gtk_widget_show(menu);
8306 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8309 void compose_update_actions_menu(Compose *compose)
8311 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8314 static void compose_update_privacy_systems_menu(Compose *compose)
8316 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8317 GSList *systems, *cur;
8319 GtkWidget *system_none;
8321 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8322 GtkWidget *privacy_menu = gtk_menu_new();
8324 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8325 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8327 g_signal_connect(G_OBJECT(system_none), "activate",
8328 G_CALLBACK(compose_set_privacy_system_cb), compose);
8330 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8331 gtk_widget_show(system_none);
8333 systems = privacy_get_system_ids();
8334 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8335 gchar *systemid = cur->data;
8337 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8338 widget = gtk_radio_menu_item_new_with_label(group,
8339 privacy_system_get_name(systemid));
8340 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8341 g_strdup(systemid), g_free);
8342 g_signal_connect(G_OBJECT(widget), "activate",
8343 G_CALLBACK(compose_set_privacy_system_cb), compose);
8345 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8346 gtk_widget_show(widget);
8349 g_slist_free(systems);
8350 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8351 gtk_widget_show_all(privacy_menu);
8352 gtk_widget_show_all(privacy_menuitem);
8355 void compose_reflect_prefs_all(void)
8360 for (cur = compose_list; cur != NULL; cur = cur->next) {
8361 compose = (Compose *)cur->data;
8362 compose_set_template_menu(compose);
8366 void compose_reflect_prefs_pixmap_theme(void)
8371 for (cur = compose_list; cur != NULL; cur = cur->next) {
8372 compose = (Compose *)cur->data;
8373 toolbar_update(TOOLBAR_COMPOSE, compose);
8377 static const gchar *compose_quote_char_from_context(Compose *compose)
8379 const gchar *qmark = NULL;
8381 cm_return_val_if_fail(compose != NULL, NULL);
8383 switch (compose->mode) {
8384 /* use forward-specific quote char */
8385 case COMPOSE_FORWARD:
8386 case COMPOSE_FORWARD_AS_ATTACH:
8387 case COMPOSE_FORWARD_INLINE:
8388 if (compose->folder && compose->folder->prefs &&
8389 compose->folder->prefs->forward_with_format)
8390 qmark = compose->folder->prefs->forward_quotemark;
8391 else if (compose->account->forward_with_format)
8392 qmark = compose->account->forward_quotemark;
8394 qmark = prefs_common.fw_quotemark;
8397 /* use reply-specific quote char in all other modes */
8399 if (compose->folder && compose->folder->prefs &&
8400 compose->folder->prefs->reply_with_format)
8401 qmark = compose->folder->prefs->reply_quotemark;
8402 else if (compose->account->reply_with_format)
8403 qmark = compose->account->reply_quotemark;
8405 qmark = prefs_common.quotemark;
8409 if (qmark == NULL || *qmark == '\0')
8415 static void compose_template_apply(Compose *compose, Template *tmpl,
8419 GtkTextBuffer *buffer;
8423 gchar *parsed_str = NULL;
8424 gint cursor_pos = 0;
8425 const gchar *err_msg = _("The body of the template has an error at line %d.");
8428 /* process the body */
8430 text = GTK_TEXT_VIEW(compose->text);
8431 buffer = gtk_text_view_get_buffer(text);
8434 qmark = compose_quote_char_from_context(compose);
8436 if (compose->replyinfo != NULL) {
8439 gtk_text_buffer_set_text(buffer, "", -1);
8440 mark = gtk_text_buffer_get_insert(buffer);
8441 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8443 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8444 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8446 } else if (compose->fwdinfo != NULL) {
8449 gtk_text_buffer_set_text(buffer, "", -1);
8450 mark = gtk_text_buffer_get_insert(buffer);
8451 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8453 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8454 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8457 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8459 GtkTextIter start, end;
8462 gtk_text_buffer_get_start_iter(buffer, &start);
8463 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8464 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8466 /* clear the buffer now */
8468 gtk_text_buffer_set_text(buffer, "", -1);
8470 parsed_str = compose_quote_fmt(compose, dummyinfo,
8471 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8472 procmsg_msginfo_free( dummyinfo );
8478 gtk_text_buffer_set_text(buffer, "", -1);
8479 mark = gtk_text_buffer_get_insert(buffer);
8480 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8483 if (replace && parsed_str && compose->account->auto_sig)
8484 compose_insert_sig(compose, FALSE);
8486 if (replace && parsed_str) {
8487 gtk_text_buffer_get_start_iter(buffer, &iter);
8488 gtk_text_buffer_place_cursor(buffer, &iter);
8492 cursor_pos = quote_fmt_get_cursor_pos();
8493 compose->set_cursor_pos = cursor_pos;
8494 if (cursor_pos == -1)
8496 gtk_text_buffer_get_start_iter(buffer, &iter);
8497 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8498 gtk_text_buffer_place_cursor(buffer, &iter);
8501 /* process the other fields */
8503 compose_template_apply_fields(compose, tmpl);
8504 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8505 quote_fmt_reset_vartable();
8506 compose_changed_cb(NULL, compose);
8509 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8510 gtkaspell_highlight_all(compose->gtkaspell);
8514 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8516 MsgInfo* dummyinfo = NULL;
8517 MsgInfo *msginfo = NULL;
8520 if (compose->replyinfo != NULL)
8521 msginfo = compose->replyinfo;
8522 else if (compose->fwdinfo != NULL)
8523 msginfo = compose->fwdinfo;
8525 dummyinfo = compose_msginfo_new_from_compose(compose);
8526 msginfo = dummyinfo;
8529 if (tmpl->from && *tmpl->from != '\0') {
8531 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8532 compose->gtkaspell);
8534 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8536 quote_fmt_scan_string(tmpl->from);
8539 buf = quote_fmt_get_buffer();
8541 alertpanel_error(_("Template From format error."));
8543 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8547 if (tmpl->to && *tmpl->to != '\0') {
8549 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8550 compose->gtkaspell);
8552 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8554 quote_fmt_scan_string(tmpl->to);
8557 buf = quote_fmt_get_buffer();
8559 alertpanel_error(_("Template To format error."));
8561 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8565 if (tmpl->cc && *tmpl->cc != '\0') {
8567 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8568 compose->gtkaspell);
8570 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8572 quote_fmt_scan_string(tmpl->cc);
8575 buf = quote_fmt_get_buffer();
8577 alertpanel_error(_("Template Cc format error."));
8579 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8583 if (tmpl->bcc && *tmpl->bcc != '\0') {
8585 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8586 compose->gtkaspell);
8588 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8590 quote_fmt_scan_string(tmpl->bcc);
8593 buf = quote_fmt_get_buffer();
8595 alertpanel_error(_("Template Bcc format error."));
8597 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8601 /* process the subject */
8602 if (tmpl->subject && *tmpl->subject != '\0') {
8604 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8605 compose->gtkaspell);
8607 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8609 quote_fmt_scan_string(tmpl->subject);
8612 buf = quote_fmt_get_buffer();
8614 alertpanel_error(_("Template subject format error."));
8616 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8620 procmsg_msginfo_free( dummyinfo );
8623 static void compose_destroy(Compose *compose)
8625 GtkAllocation allocation;
8626 GtkTextBuffer *buffer;
8627 GtkClipboard *clipboard;
8629 compose_list = g_list_remove(compose_list, compose);
8631 if (compose->updating) {
8632 debug_print("danger, not destroying anything now\n");
8633 compose->deferred_destroy = TRUE;
8636 /* NOTE: address_completion_end() does nothing with the window
8637 * however this may change. */
8638 address_completion_end(compose->window);
8640 slist_free_strings_full(compose->to_list);
8641 slist_free_strings_full(compose->newsgroup_list);
8642 slist_free_strings_full(compose->header_list);
8644 slist_free_strings_full(extra_headers);
8645 extra_headers = NULL;
8647 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8649 g_hash_table_destroy(compose->email_hashtable);
8651 procmsg_msginfo_free(compose->targetinfo);
8652 procmsg_msginfo_free(compose->replyinfo);
8653 procmsg_msginfo_free(compose->fwdinfo);
8655 g_free(compose->replyto);
8656 g_free(compose->cc);
8657 g_free(compose->bcc);
8658 g_free(compose->newsgroups);
8659 g_free(compose->followup_to);
8661 g_free(compose->ml_post);
8663 g_free(compose->inreplyto);
8664 g_free(compose->references);
8665 g_free(compose->msgid);
8666 g_free(compose->boundary);
8668 g_free(compose->redirect_filename);
8669 if (compose->undostruct)
8670 undo_destroy(compose->undostruct);
8672 g_free(compose->sig_str);
8674 g_free(compose->exteditor_file);
8676 g_free(compose->orig_charset);
8678 g_free(compose->privacy_system);
8680 #ifndef USE_NEW_ADDRBOOK
8681 if (addressbook_get_target_compose() == compose)
8682 addressbook_set_target_compose(NULL);
8685 if (compose->gtkaspell) {
8686 gtkaspell_delete(compose->gtkaspell);
8687 compose->gtkaspell = NULL;
8691 if (!compose->batch) {
8692 gtk_widget_get_allocation(compose->window, &allocation);
8693 prefs_common.compose_width = allocation.width;
8694 prefs_common.compose_height = allocation.height;
8697 if (!gtk_widget_get_parent(compose->paned))
8698 gtk_widget_destroy(compose->paned);
8699 gtk_widget_destroy(compose->popupmenu);
8701 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8702 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8703 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8705 gtk_widget_destroy(compose->window);
8706 toolbar_destroy(compose->toolbar);
8707 g_free(compose->toolbar);
8708 cm_mutex_free(compose->mutex);
8712 static void compose_attach_info_free(AttachInfo *ainfo)
8714 g_free(ainfo->file);
8715 g_free(ainfo->content_type);
8716 g_free(ainfo->name);
8717 g_free(ainfo->charset);
8721 static void compose_attach_update_label(Compose *compose)
8726 GtkTreeModel *model;
8731 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8732 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8733 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8737 while(gtk_tree_model_iter_next(model, &iter))
8740 text = g_strdup_printf("(%d)", i);
8741 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8745 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8747 Compose *compose = (Compose *)data;
8748 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8749 GtkTreeSelection *selection;
8751 GtkTreeModel *model;
8753 selection = gtk_tree_view_get_selection(tree_view);
8754 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8759 for (cur = sel; cur != NULL; cur = cur->next) {
8760 GtkTreePath *path = cur->data;
8761 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8764 gtk_tree_path_free(path);
8767 for (cur = sel; cur != NULL; cur = cur->next) {
8768 GtkTreeRowReference *ref = cur->data;
8769 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8772 if (gtk_tree_model_get_iter(model, &iter, path))
8773 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8775 gtk_tree_path_free(path);
8776 gtk_tree_row_reference_free(ref);
8780 compose_attach_update_label(compose);
8783 static struct _AttachProperty
8786 GtkWidget *mimetype_entry;
8787 GtkWidget *encoding_optmenu;
8788 GtkWidget *path_entry;
8789 GtkWidget *filename_entry;
8791 GtkWidget *cancel_btn;
8794 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8796 gtk_tree_path_free((GtkTreePath *)ptr);
8799 static void compose_attach_property(GtkAction *action, gpointer data)
8801 Compose *compose = (Compose *)data;
8802 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8804 GtkComboBox *optmenu;
8805 GtkTreeSelection *selection;
8807 GtkTreeModel *model;
8810 static gboolean cancelled;
8812 /* only if one selected */
8813 selection = gtk_tree_view_get_selection(tree_view);
8814 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8817 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8821 path = (GtkTreePath *) sel->data;
8822 gtk_tree_model_get_iter(model, &iter, path);
8823 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8826 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8832 if (!attach_prop.window)
8833 compose_attach_property_create(&cancelled);
8834 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8835 gtk_widget_grab_focus(attach_prop.ok_btn);
8836 gtk_widget_show(attach_prop.window);
8837 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8838 GTK_WINDOW(compose->window));
8840 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8841 if (ainfo->encoding == ENC_UNKNOWN)
8842 combobox_select_by_data(optmenu, ENC_BASE64);
8844 combobox_select_by_data(optmenu, ainfo->encoding);
8846 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8847 ainfo->content_type ? ainfo->content_type : "");
8848 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8849 ainfo->file ? ainfo->file : "");
8850 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8851 ainfo->name ? ainfo->name : "");
8854 const gchar *entry_text;
8856 gchar *cnttype = NULL;
8863 gtk_widget_hide(attach_prop.window);
8864 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8869 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8870 if (*entry_text != '\0') {
8873 text = g_strstrip(g_strdup(entry_text));
8874 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8875 cnttype = g_strdup(text);
8878 alertpanel_error(_("Invalid MIME type."));
8884 ainfo->encoding = combobox_get_active_data(optmenu);
8886 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8887 if (*entry_text != '\0') {
8888 if (is_file_exist(entry_text) &&
8889 (size = get_file_size(entry_text)) > 0)
8890 file = g_strdup(entry_text);
8893 (_("File doesn't exist or is empty."));
8899 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8900 if (*entry_text != '\0') {
8901 g_free(ainfo->name);
8902 ainfo->name = g_strdup(entry_text);
8906 g_free(ainfo->content_type);
8907 ainfo->content_type = cnttype;
8910 g_free(ainfo->file);
8914 ainfo->size = (goffset)size;
8916 /* update tree store */
8917 text = to_human_readable(ainfo->size);
8918 gtk_tree_model_get_iter(model, &iter, path);
8919 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8920 COL_MIMETYPE, ainfo->content_type,
8922 COL_NAME, ainfo->name,
8923 COL_CHARSET, ainfo->charset,
8929 gtk_tree_path_free(path);
8932 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8934 label = gtk_label_new(str); \
8935 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8936 GTK_FILL, 0, 0, 0); \
8937 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8939 entry = gtk_entry_new(); \
8940 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8941 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8944 static void compose_attach_property_create(gboolean *cancelled)
8950 GtkWidget *mimetype_entry;
8953 GtkListStore *optmenu_menu;
8954 GtkWidget *path_entry;
8955 GtkWidget *filename_entry;
8958 GtkWidget *cancel_btn;
8959 GList *mime_type_list, *strlist;
8962 debug_print("Creating attach_property window...\n");
8964 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8965 gtk_widget_set_size_request(window, 480, -1);
8966 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8967 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8968 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8969 g_signal_connect(G_OBJECT(window), "delete_event",
8970 G_CALLBACK(attach_property_delete_event),
8972 g_signal_connect(G_OBJECT(window), "key_press_event",
8973 G_CALLBACK(attach_property_key_pressed),
8976 vbox = gtk_vbox_new(FALSE, 8);
8977 gtk_container_add(GTK_CONTAINER(window), vbox);
8979 table = gtk_table_new(4, 2, FALSE);
8980 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8981 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8982 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8984 label = gtk_label_new(_("MIME type"));
8985 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8987 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8988 #if !GTK_CHECK_VERSION(2, 24, 0)
8989 mimetype_entry = gtk_combo_box_entry_new_text();
8991 mimetype_entry = gtk_combo_box_text_new_with_entry();
8993 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8994 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8996 /* stuff with list */
8997 mime_type_list = procmime_get_mime_type_list();
8999 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9000 MimeType *type = (MimeType *) mime_type_list->data;
9003 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9005 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9008 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9009 (GCompareFunc)strcmp2);
9012 for (mime_type_list = strlist; mime_type_list != NULL;
9013 mime_type_list = mime_type_list->next) {
9014 #if !GTK_CHECK_VERSION(2, 24, 0)
9015 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9017 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9019 g_free(mime_type_list->data);
9021 g_list_free(strlist);
9022 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9023 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9025 label = gtk_label_new(_("Encoding"));
9026 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9028 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9030 hbox = gtk_hbox_new(FALSE, 0);
9031 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9032 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9034 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9035 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9037 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9038 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9039 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9040 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9041 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9043 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9045 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9046 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9048 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9049 &ok_btn, GTK_STOCK_OK,
9051 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9052 gtk_widget_grab_default(ok_btn);
9054 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9055 G_CALLBACK(attach_property_ok),
9057 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9058 G_CALLBACK(attach_property_cancel),
9061 gtk_widget_show_all(vbox);
9063 attach_prop.window = window;
9064 attach_prop.mimetype_entry = mimetype_entry;
9065 attach_prop.encoding_optmenu = optmenu;
9066 attach_prop.path_entry = path_entry;
9067 attach_prop.filename_entry = filename_entry;
9068 attach_prop.ok_btn = ok_btn;
9069 attach_prop.cancel_btn = cancel_btn;
9072 #undef SET_LABEL_AND_ENTRY
9074 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9080 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9086 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9087 gboolean *cancelled)
9095 static gboolean attach_property_key_pressed(GtkWidget *widget,
9097 gboolean *cancelled)
9099 if (event && event->keyval == GDK_KEY_Escape) {
9103 if (event && event->keyval == GDK_KEY_Return) {
9111 static void compose_exec_ext_editor(Compose *compose)
9118 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9119 G_DIR_SEPARATOR, compose);
9121 if (pipe(pipe_fds) < 0) {
9127 if ((pid = fork()) < 0) {
9134 /* close the write side of the pipe */
9137 compose->exteditor_file = g_strdup(tmp);
9138 compose->exteditor_pid = pid;
9140 compose_set_ext_editor_sensitive(compose, FALSE);
9143 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9145 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9147 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9151 } else { /* process-monitoring process */
9157 /* close the read side of the pipe */
9160 if (compose_write_body_to_file(compose, tmp) < 0) {
9161 fd_write_all(pipe_fds[1], "2\n", 2);
9165 pid_ed = compose_exec_ext_editor_real(tmp);
9167 fd_write_all(pipe_fds[1], "1\n", 2);
9171 /* wait until editor is terminated */
9172 waitpid(pid_ed, NULL, 0);
9174 fd_write_all(pipe_fds[1], "0\n", 2);
9181 #endif /* G_OS_UNIX */
9185 static gint compose_exec_ext_editor_real(const gchar *file)
9192 cm_return_val_if_fail(file != NULL, -1);
9194 if ((pid = fork()) < 0) {
9199 if (pid != 0) return pid;
9201 /* grandchild process */
9203 if (setpgid(0, getppid()))
9206 if (prefs_common_get_ext_editor_cmd() &&
9207 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9208 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9209 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9211 if (prefs_common_get_ext_editor_cmd())
9212 g_warning("External editor command-line is invalid: '%s'\n",
9213 prefs_common_get_ext_editor_cmd());
9214 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9217 cmdline = strsplit_with_quote(buf, " ", 1024);
9218 execvp(cmdline[0], cmdline);
9221 g_strfreev(cmdline);
9226 static gboolean compose_ext_editor_kill(Compose *compose)
9228 pid_t pgid = compose->exteditor_pid * -1;
9231 ret = kill(pgid, 0);
9233 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9237 msg = g_strdup_printf
9238 (_("The external editor is still working.\n"
9239 "Force terminating the process?\n"
9240 "process group id: %d"), -pgid);
9241 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9242 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9246 if (val == G_ALERTALTERNATE) {
9247 g_source_remove(compose->exteditor_tag);
9248 g_io_channel_shutdown(compose->exteditor_ch,
9250 g_io_channel_unref(compose->exteditor_ch);
9252 if (kill(pgid, SIGTERM) < 0) perror("kill");
9253 waitpid(compose->exteditor_pid, NULL, 0);
9255 g_warning("Terminated process group id: %d", -pgid);
9256 g_warning("Temporary file: %s",
9257 compose->exteditor_file);
9259 compose_set_ext_editor_sensitive(compose, TRUE);
9261 g_free(compose->exteditor_file);
9262 compose->exteditor_file = NULL;
9263 compose->exteditor_pid = -1;
9264 compose->exteditor_ch = NULL;
9265 compose->exteditor_tag = -1;
9273 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9277 Compose *compose = (Compose *)data;
9280 debug_print("Compose: input from monitoring process\n");
9282 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9284 g_io_channel_shutdown(source, FALSE, NULL);
9285 g_io_channel_unref(source);
9287 waitpid(compose->exteditor_pid, NULL, 0);
9289 if (buf[0] == '0') { /* success */
9290 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9291 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9293 gtk_text_buffer_set_text(buffer, "", -1);
9294 compose_insert_file(compose, compose->exteditor_file);
9295 compose_changed_cb(NULL, compose);
9296 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9298 if (claws_unlink(compose->exteditor_file) < 0)
9299 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9300 } else if (buf[0] == '1') { /* failed */
9301 g_warning("Couldn't exec external editor\n");
9302 if (claws_unlink(compose->exteditor_file) < 0)
9303 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9304 } else if (buf[0] == '2') {
9305 g_warning("Couldn't write to file\n");
9306 } else if (buf[0] == '3') {
9307 g_warning("Pipe read failed\n");
9310 compose_set_ext_editor_sensitive(compose, TRUE);
9312 g_free(compose->exteditor_file);
9313 compose->exteditor_file = NULL;
9314 compose->exteditor_pid = -1;
9315 compose->exteditor_ch = NULL;
9316 compose->exteditor_tag = -1;
9321 static void compose_set_ext_editor_sensitive(Compose *compose,
9324 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9325 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9326 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9327 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9328 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9329 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9330 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9331 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9333 gtk_widget_set_sensitive(compose->text, sensitive);
9334 if (compose->toolbar->send_btn)
9335 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9336 if (compose->toolbar->sendl_btn)
9337 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9338 if (compose->toolbar->draft_btn)
9339 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9340 if (compose->toolbar->insert_btn)
9341 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9342 if (compose->toolbar->sig_btn)
9343 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9344 if (compose->toolbar->exteditor_btn)
9345 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9346 if (compose->toolbar->linewrap_current_btn)
9347 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9348 if (compose->toolbar->linewrap_all_btn)
9349 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9351 #endif /* G_OS_UNIX */
9354 * compose_undo_state_changed:
9356 * Change the sensivity of the menuentries undo and redo
9358 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9359 gint redo_state, gpointer data)
9361 Compose *compose = (Compose *)data;
9363 switch (undo_state) {
9364 case UNDO_STATE_TRUE:
9365 if (!undostruct->undo_state) {
9366 undostruct->undo_state = TRUE;
9367 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9370 case UNDO_STATE_FALSE:
9371 if (undostruct->undo_state) {
9372 undostruct->undo_state = FALSE;
9373 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9376 case UNDO_STATE_UNCHANGED:
9378 case UNDO_STATE_REFRESH:
9379 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9382 g_warning("Undo state not recognized");
9386 switch (redo_state) {
9387 case UNDO_STATE_TRUE:
9388 if (!undostruct->redo_state) {
9389 undostruct->redo_state = TRUE;
9390 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9393 case UNDO_STATE_FALSE:
9394 if (undostruct->redo_state) {
9395 undostruct->redo_state = FALSE;
9396 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9399 case UNDO_STATE_UNCHANGED:
9401 case UNDO_STATE_REFRESH:
9402 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9405 g_warning("Redo state not recognized");
9410 /* callback functions */
9412 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9413 GtkAllocation *allocation,
9416 prefs_common.compose_notebook_height = allocation->height;
9419 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9420 * includes "non-client" (windows-izm) in calculation, so this calculation
9421 * may not be accurate.
9423 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9424 GtkAllocation *allocation,
9425 GtkSHRuler *shruler)
9427 if (prefs_common.show_ruler) {
9428 gint char_width = 0, char_height = 0;
9429 gint line_width_in_chars;
9431 gtkut_get_font_size(GTK_WIDGET(widget),
9432 &char_width, &char_height);
9433 line_width_in_chars =
9434 (allocation->width - allocation->x) / char_width;
9436 /* got the maximum */
9437 gtk_shruler_set_range(GTK_SHRULER(shruler),
9438 0.0, line_width_in_chars, 0);
9447 ComposePrefType type;
9448 gboolean entry_marked;
9451 static void account_activated(GtkComboBox *optmenu, gpointer data)
9453 Compose *compose = (Compose *)data;
9456 gchar *folderidentifier;
9457 gint account_id = 0;
9460 GSList *list, *saved_list = NULL;
9461 HeaderEntryState *state;
9462 GtkRcStyle *style = NULL;
9463 #if !GTK_CHECK_VERSION(3, 0, 0)
9464 static GdkColor yellow;
9465 static gboolean color_set = FALSE;
9467 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9470 /* Get ID of active account in the combo box */
9471 menu = gtk_combo_box_get_model(optmenu);
9472 gtk_combo_box_get_active_iter(optmenu, &iter);
9473 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9475 ac = account_find_from_id(account_id);
9476 cm_return_if_fail(ac != NULL);
9478 if (ac != compose->account) {
9479 compose_select_account(compose, ac, FALSE);
9481 for (list = compose->header_list; list; list = list->next) {
9482 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9484 if (hentry->type == PREF_ACCOUNT || !list->next) {
9485 compose_destroy_headerentry(compose, hentry);
9489 state = g_malloc0(sizeof(HeaderEntryState));
9490 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9491 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9492 state->entry = gtk_editable_get_chars(
9493 GTK_EDITABLE(hentry->entry), 0, -1);
9494 state->type = hentry->type;
9496 #if !GTK_CHECK_VERSION(3, 0, 0)
9498 gdk_color_parse("#f5f6be", &yellow);
9499 color_set = gdk_colormap_alloc_color(
9500 gdk_colormap_get_system(),
9501 &yellow, FALSE, TRUE);
9505 style = gtk_widget_get_modifier_style(hentry->entry);
9506 state->entry_marked = gdk_color_equal(&yellow,
9507 &style->base[GTK_STATE_NORMAL]);
9509 saved_list = g_slist_append(saved_list, state);
9510 compose_destroy_headerentry(compose, hentry);
9513 compose->header_last = NULL;
9514 g_slist_free(compose->header_list);
9515 compose->header_list = NULL;
9516 compose->header_nextrow = 1;
9517 compose_create_header_entry(compose);
9519 if (ac->set_autocc && ac->auto_cc)
9520 compose_entry_append(compose, ac->auto_cc,
9521 COMPOSE_CC, PREF_ACCOUNT);
9523 if (ac->set_autobcc && ac->auto_bcc)
9524 compose_entry_append(compose, ac->auto_bcc,
9525 COMPOSE_BCC, PREF_ACCOUNT);
9527 if (ac->set_autoreplyto && ac->auto_replyto)
9528 compose_entry_append(compose, ac->auto_replyto,
9529 COMPOSE_REPLYTO, PREF_ACCOUNT);
9531 for (list = saved_list; list; list = list->next) {
9532 state = (HeaderEntryState *) list->data;
9534 compose_add_header_entry(compose, state->header,
9535 state->entry, state->type);
9536 if (state->entry_marked)
9537 compose_entry_mark_default_to(compose, state->entry);
9539 g_free(state->header);
9540 g_free(state->entry);
9543 g_slist_free(saved_list);
9545 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9546 (ac->protocol == A_NNTP) ?
9547 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9550 /* Set message save folder */
9551 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9552 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9554 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9555 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9557 compose_set_save_to(compose, NULL);
9558 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9559 folderidentifier = folder_item_get_identifier(account_get_special_folder
9560 (compose->account, F_OUTBOX));
9561 compose_set_save_to(compose, folderidentifier);
9562 g_free(folderidentifier);
9566 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9567 GtkTreeViewColumn *column, Compose *compose)
9569 compose_attach_property(NULL, compose);
9572 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9575 Compose *compose = (Compose *)data;
9576 GtkTreeSelection *attach_selection;
9577 gint attach_nr_selected;
9579 if (!event) return FALSE;
9581 if (event->button == 3) {
9582 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9583 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9585 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9586 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9588 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9589 NULL, NULL, event->button, event->time);
9596 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9599 Compose *compose = (Compose *)data;
9601 if (!event) return FALSE;
9603 switch (event->keyval) {
9604 case GDK_KEY_Delete:
9605 compose_attach_remove_selected(NULL, compose);
9611 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9613 toolbar_comp_set_sensitive(compose, allow);
9614 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9615 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9617 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9619 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9620 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9621 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9623 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9627 static void compose_send_cb(GtkAction *action, gpointer data)
9629 Compose *compose = (Compose *)data;
9631 if (prefs_common.work_offline &&
9632 !inc_offline_should_override(TRUE,
9633 _("Claws Mail needs network access in order "
9634 "to send this email.")))
9637 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9638 g_source_remove(compose->draft_timeout_tag);
9639 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9642 compose_send(compose);
9645 static void compose_send_later_cb(GtkAction *action, gpointer data)
9647 Compose *compose = (Compose *)data;
9651 compose_allow_user_actions(compose, FALSE);
9652 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9653 compose_allow_user_actions(compose, TRUE);
9657 compose_close(compose);
9658 } else if (val == -1) {
9659 alertpanel_error(_("Could not queue message."));
9660 } else if (val == -2) {
9661 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9662 } else if (val == -3) {
9663 if (privacy_peek_error())
9664 alertpanel_error(_("Could not queue message for sending:\n\n"
9665 "Signature failed: %s"), privacy_get_error());
9666 } else if (val == -4) {
9667 alertpanel_error(_("Could not queue message for sending:\n\n"
9668 "Charset conversion failed."));
9669 } else if (val == -5) {
9670 alertpanel_error(_("Could not queue message for sending:\n\n"
9671 "Couldn't get recipient encryption key."));
9672 } else if (val == -6) {
9675 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9678 #define DRAFTED_AT_EXIT "drafted_at_exit"
9679 static void compose_register_draft(MsgInfo *info)
9681 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9682 DRAFTED_AT_EXIT, NULL);
9683 FILE *fp = g_fopen(filepath, "ab");
9686 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9694 gboolean compose_draft (gpointer data, guint action)
9696 Compose *compose = (Compose *)data;
9701 MsgFlags flag = {0, 0};
9702 static gboolean lock = FALSE;
9703 MsgInfo *newmsginfo;
9705 gboolean target_locked = FALSE;
9706 gboolean err = FALSE;
9708 if (lock) return FALSE;
9710 if (compose->sending)
9713 draft = account_get_special_folder(compose->account, F_DRAFT);
9714 cm_return_val_if_fail(draft != NULL, FALSE);
9716 if (!g_mutex_trylock(compose->mutex)) {
9717 /* we don't want to lock the mutex once it's available,
9718 * because as the only other part of compose.c locking
9719 * it is compose_close - which means once unlocked,
9720 * the compose struct will be freed */
9721 debug_print("couldn't lock mutex, probably sending\n");
9727 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9728 G_DIR_SEPARATOR, compose);
9729 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9730 FILE_OP_ERROR(tmp, "fopen");
9734 /* chmod for security */
9735 if (change_file_mode_rw(fp, tmp) < 0) {
9736 FILE_OP_ERROR(tmp, "chmod");
9737 g_warning("can't change file mode\n");
9740 /* Save draft infos */
9741 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9742 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9744 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9745 gchar *savefolderid;
9747 savefolderid = compose_get_save_to(compose);
9748 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9749 g_free(savefolderid);
9751 if (compose->return_receipt) {
9752 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9754 if (compose->privacy_system) {
9755 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9756 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9757 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9760 /* Message-ID of message replying to */
9761 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9764 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9765 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9768 /* Message-ID of message forwarding to */
9769 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9772 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9773 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9777 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9778 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9780 sheaders = compose_get_manual_headers_info(compose);
9781 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9784 /* end of headers */
9785 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9792 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9796 if (fclose(fp) == EOF) {
9800 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9801 if (compose->targetinfo) {
9802 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9804 flag.perm_flags |= MSG_LOCKED;
9806 flag.tmp_flags = MSG_DRAFT;
9808 folder_item_scan(draft);
9809 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9810 MsgInfo *tmpinfo = NULL;
9811 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9812 if (compose->msgid) {
9813 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9816 msgnum = tmpinfo->msgnum;
9817 procmsg_msginfo_free(tmpinfo);
9818 debug_print("got draft msgnum %d from scanning\n", msgnum);
9820 debug_print("didn't get draft msgnum after scanning\n");
9823 debug_print("got draft msgnum %d from adding\n", msgnum);
9829 if (action != COMPOSE_AUTO_SAVE) {
9830 if (action != COMPOSE_DRAFT_FOR_EXIT)
9831 alertpanel_error(_("Could not save draft."));
9834 gtkut_window_popup(compose->window);
9835 val = alertpanel_full(_("Could not save draft"),
9836 _("Could not save draft.\n"
9837 "Do you want to cancel exit or discard this email?"),
9838 _("_Cancel exit"), _("_Discard email"), NULL,
9839 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9840 if (val == G_ALERTALTERNATE) {
9842 g_mutex_unlock(compose->mutex); /* must be done before closing */
9843 compose_close(compose);
9847 g_mutex_unlock(compose->mutex); /* must be done before closing */
9856 if (compose->mode == COMPOSE_REEDIT) {
9857 compose_remove_reedit_target(compose, TRUE);
9860 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9863 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9865 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9867 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9868 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9869 procmsg_msginfo_set_flags(newmsginfo, 0,
9870 MSG_HAS_ATTACHMENT);
9872 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9873 compose_register_draft(newmsginfo);
9875 procmsg_msginfo_free(newmsginfo);
9878 folder_item_scan(draft);
9880 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9882 g_mutex_unlock(compose->mutex); /* must be done before closing */
9883 compose_close(compose);
9889 path = folder_item_fetch_msg(draft, msgnum);
9891 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9894 if (g_stat(path, &s) < 0) {
9895 FILE_OP_ERROR(path, "stat");
9901 procmsg_msginfo_free(compose->targetinfo);
9902 compose->targetinfo = procmsg_msginfo_new();
9903 compose->targetinfo->msgnum = msgnum;
9904 compose->targetinfo->size = (goffset)s.st_size;
9905 compose->targetinfo->mtime = s.st_mtime;
9906 compose->targetinfo->folder = draft;
9908 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9909 compose->mode = COMPOSE_REEDIT;
9911 if (action == COMPOSE_AUTO_SAVE) {
9912 compose->autosaved_draft = compose->targetinfo;
9914 compose->modified = FALSE;
9915 compose_set_title(compose);
9919 g_mutex_unlock(compose->mutex);
9923 void compose_clear_exit_drafts(void)
9925 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9926 DRAFTED_AT_EXIT, NULL);
9927 if (is_file_exist(filepath))
9928 claws_unlink(filepath);
9933 void compose_reopen_exit_drafts(void)
9935 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9936 DRAFTED_AT_EXIT, NULL);
9937 FILE *fp = g_fopen(filepath, "rb");
9941 while (fgets(buf, sizeof(buf), fp)) {
9942 gchar **parts = g_strsplit(buf, "\t", 2);
9943 const gchar *folder = parts[0];
9944 int msgnum = parts[1] ? atoi(parts[1]):-1;
9946 if (folder && *folder && msgnum > -1) {
9947 FolderItem *item = folder_find_item_from_identifier(folder);
9948 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9950 compose_reedit(info, FALSE);
9957 compose_clear_exit_drafts();
9960 static void compose_save_cb(GtkAction *action, gpointer data)
9962 Compose *compose = (Compose *)data;
9963 compose_draft(compose, COMPOSE_KEEP_EDITING);
9964 compose->rmode = COMPOSE_REEDIT;
9967 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9969 if (compose && file_list) {
9972 for ( tmp = file_list; tmp; tmp = tmp->next) {
9973 gchar *file = (gchar *) tmp->data;
9974 gchar *utf8_filename = conv_filename_to_utf8(file);
9975 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9976 compose_changed_cb(NULL, compose);
9981 g_free(utf8_filename);
9986 static void compose_attach_cb(GtkAction *action, gpointer data)
9988 Compose *compose = (Compose *)data;
9991 if (compose->redirect_filename != NULL)
9994 /* Set focus_window properly, in case we were called via popup menu,
9995 * which unsets it (via focus_out_event callback on compose window). */
9996 manage_window_focus_in(compose->window, NULL, NULL);
9998 file_list = filesel_select_multiple_files_open(_("Select file"));
10001 compose_attach_from_list(compose, file_list, TRUE);
10002 g_list_free(file_list);
10006 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10008 Compose *compose = (Compose *)data;
10010 gint files_inserted = 0;
10012 file_list = filesel_select_multiple_files_open(_("Select file"));
10017 for ( tmp = file_list; tmp; tmp = tmp->next) {
10018 gchar *file = (gchar *) tmp->data;
10019 gchar *filedup = g_strdup(file);
10020 gchar *shortfile = g_path_get_basename(filedup);
10021 ComposeInsertResult res;
10022 /* insert the file if the file is short or if the user confirmed that
10023 he/she wants to insert the large file */
10024 res = compose_insert_file(compose, file);
10025 if (res == COMPOSE_INSERT_READ_ERROR) {
10026 alertpanel_error(_("File '%s' could not be read."), shortfile);
10027 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10028 alertpanel_error(_("File '%s' contained invalid characters\n"
10029 "for the current encoding, insertion may be incorrect."),
10031 } else if (res == COMPOSE_INSERT_SUCCESS)
10038 g_list_free(file_list);
10042 if (files_inserted > 0 && compose->gtkaspell &&
10043 compose->gtkaspell->check_while_typing)
10044 gtkaspell_highlight_all(compose->gtkaspell);
10048 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10050 Compose *compose = (Compose *)data;
10052 compose_insert_sig(compose, FALSE);
10055 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10057 Compose *compose = (Compose *)data;
10059 compose_insert_sig(compose, TRUE);
10062 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10066 Compose *compose = (Compose *)data;
10068 gtkut_widget_get_uposition(widget, &x, &y);
10069 if (!compose->batch) {
10070 prefs_common.compose_x = x;
10071 prefs_common.compose_y = y;
10073 if (compose->sending || compose->updating)
10075 compose_close_cb(NULL, compose);
10079 void compose_close_toolbar(Compose *compose)
10081 compose_close_cb(NULL, compose);
10084 static gboolean compose_can_autosave(Compose *compose)
10086 if (compose->privacy_system && compose->use_encryption)
10087 return prefs_common.autosave && prefs_common.autosave_encrypted;
10089 return prefs_common.autosave;
10092 static void compose_close_cb(GtkAction *action, gpointer data)
10094 Compose *compose = (Compose *)data;
10098 if (compose->exteditor_tag != -1) {
10099 if (!compose_ext_editor_kill(compose))
10104 if (compose->modified) {
10105 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10106 if (!g_mutex_trylock(compose->mutex)) {
10107 /* we don't want to lock the mutex once it's available,
10108 * because as the only other part of compose.c locking
10109 * it is compose_close - which means once unlocked,
10110 * the compose struct will be freed */
10111 debug_print("couldn't lock mutex, probably sending\n");
10115 val = alertpanel(_("Discard message"),
10116 _("This message has been modified. Discard it?"),
10117 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10119 val = alertpanel(_("Save changes"),
10120 _("This message has been modified. Save the latest changes?"),
10121 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10123 g_mutex_unlock(compose->mutex);
10125 case G_ALERTDEFAULT:
10126 if (compose_can_autosave(compose) && !reedit)
10127 compose_remove_draft(compose);
10129 case G_ALERTALTERNATE:
10130 compose_draft(data, COMPOSE_QUIT_EDITING);
10137 compose_close(compose);
10140 static void compose_print_cb(GtkAction *action, gpointer data)
10142 Compose *compose = (Compose *) data;
10144 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10145 if (compose->targetinfo)
10146 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10149 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10151 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10152 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10153 Compose *compose = (Compose *) data;
10156 compose->out_encoding = (CharSet)value;
10159 static void compose_address_cb(GtkAction *action, gpointer data)
10161 Compose *compose = (Compose *)data;
10163 #ifndef USE_NEW_ADDRBOOK
10164 addressbook_open(compose);
10166 GError* error = NULL;
10167 addressbook_connect_signals(compose);
10168 addressbook_dbus_open(TRUE, &error);
10170 g_warning("%s", error->message);
10171 g_error_free(error);
10176 static void about_show_cb(GtkAction *action, gpointer data)
10181 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10183 Compose *compose = (Compose *)data;
10188 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10189 cm_return_if_fail(tmpl != NULL);
10191 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10193 val = alertpanel(_("Apply template"), msg,
10194 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10197 if (val == G_ALERTDEFAULT)
10198 compose_template_apply(compose, tmpl, TRUE);
10199 else if (val == G_ALERTALTERNATE)
10200 compose_template_apply(compose, tmpl, FALSE);
10203 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10205 Compose *compose = (Compose *)data;
10207 compose_exec_ext_editor(compose);
10210 static void compose_undo_cb(GtkAction *action, gpointer data)
10212 Compose *compose = (Compose *)data;
10213 gboolean prev_autowrap = compose->autowrap;
10215 compose->autowrap = FALSE;
10216 undo_undo(compose->undostruct);
10217 compose->autowrap = prev_autowrap;
10220 static void compose_redo_cb(GtkAction *action, gpointer data)
10222 Compose *compose = (Compose *)data;
10223 gboolean prev_autowrap = compose->autowrap;
10225 compose->autowrap = FALSE;
10226 undo_redo(compose->undostruct);
10227 compose->autowrap = prev_autowrap;
10230 static void entry_cut_clipboard(GtkWidget *entry)
10232 if (GTK_IS_EDITABLE(entry))
10233 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10234 else if (GTK_IS_TEXT_VIEW(entry))
10235 gtk_text_buffer_cut_clipboard(
10236 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10237 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10241 static void entry_copy_clipboard(GtkWidget *entry)
10243 if (GTK_IS_EDITABLE(entry))
10244 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10245 else if (GTK_IS_TEXT_VIEW(entry))
10246 gtk_text_buffer_copy_clipboard(
10247 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10248 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10251 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10252 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10254 if (GTK_IS_TEXT_VIEW(entry)) {
10255 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10256 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10257 GtkTextIter start_iter, end_iter;
10259 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10261 if (contents == NULL)
10264 /* we shouldn't delete the selection when middle-click-pasting, or we
10265 * can't mid-click-paste our own selection */
10266 if (clip != GDK_SELECTION_PRIMARY) {
10267 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10268 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10271 if (insert_place == NULL) {
10272 /* if insert_place isn't specified, insert at the cursor.
10273 * used for Ctrl-V pasting */
10274 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10275 start = gtk_text_iter_get_offset(&start_iter);
10276 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10278 /* if insert_place is specified, paste here.
10279 * used for mid-click-pasting */
10280 start = gtk_text_iter_get_offset(insert_place);
10281 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10282 if (prefs_common.primary_paste_unselects)
10283 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10287 /* paste unwrapped: mark the paste so it's not wrapped later */
10288 end = start + strlen(contents);
10289 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10290 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10291 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10292 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10293 /* rewrap paragraph now (after a mid-click-paste) */
10294 mark_start = gtk_text_buffer_get_insert(buffer);
10295 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10296 gtk_text_iter_backward_char(&start_iter);
10297 compose_beautify_paragraph(compose, &start_iter, TRUE);
10299 } else if (GTK_IS_EDITABLE(entry))
10300 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10302 compose->modified = TRUE;
10305 static void entry_allsel(GtkWidget *entry)
10307 if (GTK_IS_EDITABLE(entry))
10308 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10309 else if (GTK_IS_TEXT_VIEW(entry)) {
10310 GtkTextIter startiter, enditer;
10311 GtkTextBuffer *textbuf;
10313 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10314 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10315 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10317 gtk_text_buffer_move_mark_by_name(textbuf,
10318 "selection_bound", &startiter);
10319 gtk_text_buffer_move_mark_by_name(textbuf,
10320 "insert", &enditer);
10324 static void compose_cut_cb(GtkAction *action, gpointer data)
10326 Compose *compose = (Compose *)data;
10327 if (compose->focused_editable
10328 #ifndef GENERIC_UMPC
10329 && gtk_widget_has_focus(compose->focused_editable)
10332 entry_cut_clipboard(compose->focused_editable);
10335 static void compose_copy_cb(GtkAction *action, gpointer data)
10337 Compose *compose = (Compose *)data;
10338 if (compose->focused_editable
10339 #ifndef GENERIC_UMPC
10340 && gtk_widget_has_focus(compose->focused_editable)
10343 entry_copy_clipboard(compose->focused_editable);
10346 static void compose_paste_cb(GtkAction *action, gpointer data)
10348 Compose *compose = (Compose *)data;
10349 gint prev_autowrap;
10350 GtkTextBuffer *buffer;
10352 if (compose->focused_editable &&
10353 #ifndef GENERIC_UMPC
10354 gtk_widget_has_focus(compose->focused_editable)
10357 entry_paste_clipboard(compose, compose->focused_editable,
10358 prefs_common.linewrap_pastes,
10359 GDK_SELECTION_CLIPBOARD, NULL);
10364 #ifndef GENERIC_UMPC
10365 gtk_widget_has_focus(compose->text) &&
10367 compose->gtkaspell &&
10368 compose->gtkaspell->check_while_typing)
10369 gtkaspell_highlight_all(compose->gtkaspell);
10373 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10375 Compose *compose = (Compose *)data;
10376 gint wrap_quote = prefs_common.linewrap_quote;
10377 if (compose->focused_editable
10378 #ifndef GENERIC_UMPC
10379 && gtk_widget_has_focus(compose->focused_editable)
10382 /* let text_insert() (called directly or at a later time
10383 * after the gtk_editable_paste_clipboard) know that
10384 * text is to be inserted as a quotation. implemented
10385 * by using a simple refcount... */
10386 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10387 G_OBJECT(compose->focused_editable),
10388 "paste_as_quotation"));
10389 g_object_set_data(G_OBJECT(compose->focused_editable),
10390 "paste_as_quotation",
10391 GINT_TO_POINTER(paste_as_quotation + 1));
10392 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10393 entry_paste_clipboard(compose, compose->focused_editable,
10394 prefs_common.linewrap_pastes,
10395 GDK_SELECTION_CLIPBOARD, NULL);
10396 prefs_common.linewrap_quote = wrap_quote;
10400 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10402 Compose *compose = (Compose *)data;
10403 gint prev_autowrap;
10404 GtkTextBuffer *buffer;
10406 if (compose->focused_editable
10407 #ifndef GENERIC_UMPC
10408 && gtk_widget_has_focus(compose->focused_editable)
10411 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10412 GDK_SELECTION_CLIPBOARD, NULL);
10417 #ifndef GENERIC_UMPC
10418 gtk_widget_has_focus(compose->text) &&
10420 compose->gtkaspell &&
10421 compose->gtkaspell->check_while_typing)
10422 gtkaspell_highlight_all(compose->gtkaspell);
10426 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10428 Compose *compose = (Compose *)data;
10429 gint prev_autowrap;
10430 GtkTextBuffer *buffer;
10432 if (compose->focused_editable
10433 #ifndef GENERIC_UMPC
10434 && gtk_widget_has_focus(compose->focused_editable)
10437 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10438 GDK_SELECTION_CLIPBOARD, NULL);
10443 #ifndef GENERIC_UMPC
10444 gtk_widget_has_focus(compose->text) &&
10446 compose->gtkaspell &&
10447 compose->gtkaspell->check_while_typing)
10448 gtkaspell_highlight_all(compose->gtkaspell);
10452 static void compose_allsel_cb(GtkAction *action, gpointer data)
10454 Compose *compose = (Compose *)data;
10455 if (compose->focused_editable
10456 #ifndef GENERIC_UMPC
10457 && gtk_widget_has_focus(compose->focused_editable)
10460 entry_allsel(compose->focused_editable);
10463 static void textview_move_beginning_of_line (GtkTextView *text)
10465 GtkTextBuffer *buffer;
10469 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10471 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10472 mark = gtk_text_buffer_get_insert(buffer);
10473 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10474 gtk_text_iter_set_line_offset(&ins, 0);
10475 gtk_text_buffer_place_cursor(buffer, &ins);
10478 static void textview_move_forward_character (GtkTextView *text)
10480 GtkTextBuffer *buffer;
10484 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10486 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10487 mark = gtk_text_buffer_get_insert(buffer);
10488 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10489 if (gtk_text_iter_forward_cursor_position(&ins))
10490 gtk_text_buffer_place_cursor(buffer, &ins);
10493 static void textview_move_backward_character (GtkTextView *text)
10495 GtkTextBuffer *buffer;
10499 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10501 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10502 mark = gtk_text_buffer_get_insert(buffer);
10503 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10504 if (gtk_text_iter_backward_cursor_position(&ins))
10505 gtk_text_buffer_place_cursor(buffer, &ins);
10508 static void textview_move_forward_word (GtkTextView *text)
10510 GtkTextBuffer *buffer;
10515 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10517 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10518 mark = gtk_text_buffer_get_insert(buffer);
10519 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10520 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10521 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10522 gtk_text_iter_backward_word_start(&ins);
10523 gtk_text_buffer_place_cursor(buffer, &ins);
10527 static void textview_move_backward_word (GtkTextView *text)
10529 GtkTextBuffer *buffer;
10533 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10535 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10536 mark = gtk_text_buffer_get_insert(buffer);
10537 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10538 if (gtk_text_iter_backward_word_starts(&ins, 1))
10539 gtk_text_buffer_place_cursor(buffer, &ins);
10542 static void textview_move_end_of_line (GtkTextView *text)
10544 GtkTextBuffer *buffer;
10548 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10550 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10551 mark = gtk_text_buffer_get_insert(buffer);
10552 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10553 if (gtk_text_iter_forward_to_line_end(&ins))
10554 gtk_text_buffer_place_cursor(buffer, &ins);
10557 static void textview_move_next_line (GtkTextView *text)
10559 GtkTextBuffer *buffer;
10564 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10566 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10567 mark = gtk_text_buffer_get_insert(buffer);
10568 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10569 offset = gtk_text_iter_get_line_offset(&ins);
10570 if (gtk_text_iter_forward_line(&ins)) {
10571 gtk_text_iter_set_line_offset(&ins, offset);
10572 gtk_text_buffer_place_cursor(buffer, &ins);
10576 static void textview_move_previous_line (GtkTextView *text)
10578 GtkTextBuffer *buffer;
10583 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10585 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10586 mark = gtk_text_buffer_get_insert(buffer);
10587 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10588 offset = gtk_text_iter_get_line_offset(&ins);
10589 if (gtk_text_iter_backward_line(&ins)) {
10590 gtk_text_iter_set_line_offset(&ins, offset);
10591 gtk_text_buffer_place_cursor(buffer, &ins);
10595 static void textview_delete_forward_character (GtkTextView *text)
10597 GtkTextBuffer *buffer;
10599 GtkTextIter ins, end_iter;
10601 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10603 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10604 mark = gtk_text_buffer_get_insert(buffer);
10605 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10607 if (gtk_text_iter_forward_char(&end_iter)) {
10608 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10612 static void textview_delete_backward_character (GtkTextView *text)
10614 GtkTextBuffer *buffer;
10616 GtkTextIter ins, end_iter;
10618 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10620 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10621 mark = gtk_text_buffer_get_insert(buffer);
10622 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10624 if (gtk_text_iter_backward_char(&end_iter)) {
10625 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10629 static void textview_delete_forward_word (GtkTextView *text)
10631 GtkTextBuffer *buffer;
10633 GtkTextIter ins, end_iter;
10635 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10637 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10638 mark = gtk_text_buffer_get_insert(buffer);
10639 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10641 if (gtk_text_iter_forward_word_end(&end_iter)) {
10642 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10646 static void textview_delete_backward_word (GtkTextView *text)
10648 GtkTextBuffer *buffer;
10650 GtkTextIter ins, end_iter;
10652 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10654 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10655 mark = gtk_text_buffer_get_insert(buffer);
10656 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10658 if (gtk_text_iter_backward_word_start(&end_iter)) {
10659 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10663 static void textview_delete_line (GtkTextView *text)
10665 GtkTextBuffer *buffer;
10667 GtkTextIter ins, start_iter, end_iter;
10669 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10671 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10672 mark = gtk_text_buffer_get_insert(buffer);
10673 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10676 gtk_text_iter_set_line_offset(&start_iter, 0);
10679 if (gtk_text_iter_ends_line(&end_iter)){
10680 if (!gtk_text_iter_forward_char(&end_iter))
10681 gtk_text_iter_backward_char(&start_iter);
10684 gtk_text_iter_forward_to_line_end(&end_iter);
10685 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10688 static void textview_delete_to_line_end (GtkTextView *text)
10690 GtkTextBuffer *buffer;
10692 GtkTextIter ins, end_iter;
10694 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10696 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10697 mark = gtk_text_buffer_get_insert(buffer);
10698 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10700 if (gtk_text_iter_ends_line(&end_iter))
10701 gtk_text_iter_forward_char(&end_iter);
10703 gtk_text_iter_forward_to_line_end(&end_iter);
10704 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10707 #define DO_ACTION(name, act) { \
10708 if(!strcmp(name, a_name)) { \
10712 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10714 const gchar *a_name = gtk_action_get_name(action);
10715 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10716 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10717 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10718 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10719 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10720 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10721 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10722 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10723 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10724 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10725 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10726 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10727 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10728 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10732 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10734 Compose *compose = (Compose *)data;
10735 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10736 ComposeCallAdvancedAction action = -1;
10738 action = compose_call_advanced_action_from_path(gaction);
10741 void (*do_action) (GtkTextView *text);
10742 } action_table[] = {
10743 {textview_move_beginning_of_line},
10744 {textview_move_forward_character},
10745 {textview_move_backward_character},
10746 {textview_move_forward_word},
10747 {textview_move_backward_word},
10748 {textview_move_end_of_line},
10749 {textview_move_next_line},
10750 {textview_move_previous_line},
10751 {textview_delete_forward_character},
10752 {textview_delete_backward_character},
10753 {textview_delete_forward_word},
10754 {textview_delete_backward_word},
10755 {textview_delete_line},
10756 {textview_delete_to_line_end}
10759 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10761 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10762 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10763 if (action_table[action].do_action)
10764 action_table[action].do_action(text);
10766 g_warning("Not implemented yet.");
10770 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10772 GtkAllocation allocation;
10776 if (GTK_IS_EDITABLE(widget)) {
10777 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10778 gtk_editable_set_position(GTK_EDITABLE(widget),
10781 if ((parent = gtk_widget_get_parent(widget))
10782 && (parent = gtk_widget_get_parent(parent))
10783 && (parent = gtk_widget_get_parent(parent))) {
10784 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10785 gtk_widget_get_allocation(widget, &allocation);
10786 gint y = allocation.y;
10787 gint height = allocation.height;
10788 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10789 (GTK_SCROLLED_WINDOW(parent));
10791 gfloat value = gtk_adjustment_get_value(shown);
10792 gfloat upper = gtk_adjustment_get_upper(shown);
10793 gfloat page_size = gtk_adjustment_get_page_size(shown);
10794 if (y < (int)value) {
10795 gtk_adjustment_set_value(shown, y - 1);
10797 if ((y + height) > ((int)value + (int)page_size)) {
10798 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10799 gtk_adjustment_set_value(shown,
10800 y + height - (int)page_size - 1);
10802 gtk_adjustment_set_value(shown,
10803 (int)upper - (int)page_size - 1);
10810 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10811 compose->focused_editable = widget;
10813 #ifdef GENERIC_UMPC
10814 if (GTK_IS_TEXT_VIEW(widget)
10815 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10816 g_object_ref(compose->notebook);
10817 g_object_ref(compose->edit_vbox);
10818 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10819 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10820 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10821 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10822 g_object_unref(compose->notebook);
10823 g_object_unref(compose->edit_vbox);
10824 g_signal_handlers_block_by_func(G_OBJECT(widget),
10825 G_CALLBACK(compose_grab_focus_cb),
10827 gtk_widget_grab_focus(widget);
10828 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10829 G_CALLBACK(compose_grab_focus_cb),
10831 } else if (!GTK_IS_TEXT_VIEW(widget)
10832 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10833 g_object_ref(compose->notebook);
10834 g_object_ref(compose->edit_vbox);
10835 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10836 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10837 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10838 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10839 g_object_unref(compose->notebook);
10840 g_object_unref(compose->edit_vbox);
10841 g_signal_handlers_block_by_func(G_OBJECT(widget),
10842 G_CALLBACK(compose_grab_focus_cb),
10844 gtk_widget_grab_focus(widget);
10845 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10846 G_CALLBACK(compose_grab_focus_cb),
10852 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10854 compose->modified = TRUE;
10855 // compose_beautify_paragraph(compose, NULL, TRUE);
10856 #ifndef GENERIC_UMPC
10857 compose_set_title(compose);
10861 static void compose_wrap_cb(GtkAction *action, gpointer data)
10863 Compose *compose = (Compose *)data;
10864 compose_beautify_paragraph(compose, NULL, TRUE);
10867 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10869 Compose *compose = (Compose *)data;
10870 compose_wrap_all_full(compose, TRUE);
10873 static void compose_find_cb(GtkAction *action, gpointer data)
10875 Compose *compose = (Compose *)data;
10877 message_search_compose(compose);
10880 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10883 Compose *compose = (Compose *)data;
10884 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10885 if (compose->autowrap)
10886 compose_wrap_all_full(compose, TRUE);
10887 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10890 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10893 Compose *compose = (Compose *)data;
10894 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10897 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10899 Compose *compose = (Compose *)data;
10901 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10904 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10906 Compose *compose = (Compose *)data;
10908 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10911 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10913 g_free(compose->privacy_system);
10915 compose->privacy_system = g_strdup(account->default_privacy_system);
10916 compose_update_privacy_system_menu_item(compose, warn);
10919 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10921 Compose *compose = (Compose *)data;
10923 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10924 gtk_widget_show(compose->ruler_hbox);
10925 prefs_common.show_ruler = TRUE;
10927 gtk_widget_hide(compose->ruler_hbox);
10928 gtk_widget_queue_resize(compose->edit_vbox);
10929 prefs_common.show_ruler = FALSE;
10933 static void compose_attach_drag_received_cb (GtkWidget *widget,
10934 GdkDragContext *context,
10937 GtkSelectionData *data,
10940 gpointer user_data)
10942 Compose *compose = (Compose *)user_data;
10946 type = gtk_selection_data_get_data_type(data);
10947 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10949 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10951 ) && gtk_drag_get_source_widget(context) !=
10952 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10953 list = uri_list_extract_filenames(
10954 (const gchar *)gtk_selection_data_get_data(data));
10955 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10956 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10957 compose_attach_append
10958 (compose, (const gchar *)tmp->data,
10959 utf8_filename, NULL, NULL);
10960 g_free(utf8_filename);
10962 if (list) compose_changed_cb(NULL, compose);
10963 list_free_strings(list);
10965 } else if (gtk_drag_get_source_widget(context)
10966 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10967 /* comes from our summaryview */
10968 SummaryView * summaryview = NULL;
10969 GSList * list = NULL, *cur = NULL;
10971 if (mainwindow_get_mainwindow())
10972 summaryview = mainwindow_get_mainwindow()->summaryview;
10975 list = summary_get_selected_msg_list(summaryview);
10977 for (cur = list; cur; cur = cur->next) {
10978 MsgInfo *msginfo = (MsgInfo *)cur->data;
10979 gchar *file = NULL;
10981 file = procmsg_get_message_file_full(msginfo,
10984 compose_attach_append(compose, (const gchar *)file,
10985 (const gchar *)file, "message/rfc822", NULL);
10989 g_slist_free(list);
10993 static gboolean compose_drag_drop(GtkWidget *widget,
10994 GdkDragContext *drag_context,
10996 guint time, gpointer user_data)
10998 /* not handling this signal makes compose_insert_drag_received_cb
11003 static gboolean completion_set_focus_to_subject
11004 (GtkWidget *widget,
11005 GdkEventKey *event,
11008 cm_return_val_if_fail(compose != NULL, FALSE);
11010 /* make backtab move to subject field */
11011 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11012 gtk_widget_grab_focus(compose->subject_entry);
11018 static void compose_insert_drag_received_cb (GtkWidget *widget,
11019 GdkDragContext *drag_context,
11022 GtkSelectionData *data,
11025 gpointer user_data)
11027 Compose *compose = (Compose *)user_data;
11031 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11033 type = gtk_selection_data_get_data_type(data);
11035 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11037 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11039 AlertValue val = G_ALERTDEFAULT;
11040 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11042 list = uri_list_extract_filenames(ddata);
11043 if (list == NULL && strstr(ddata, "://")) {
11044 /* Assume a list of no files, and data has ://, is a remote link */
11045 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11046 gchar *tmpfile = get_tmp_file();
11047 str_write_to_file(tmpdata, tmpfile);
11049 compose_insert_file(compose, tmpfile);
11050 claws_unlink(tmpfile);
11052 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11053 compose_beautify_paragraph(compose, NULL, TRUE);
11056 switch (prefs_common.compose_dnd_mode) {
11057 case COMPOSE_DND_ASK:
11058 val = alertpanel_full(_("Insert or attach?"),
11059 _("Do you want to insert the contents of the file(s) "
11060 "into the message body, or attach it to the email?"),
11061 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11062 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11064 case COMPOSE_DND_INSERT:
11065 val = G_ALERTALTERNATE;
11067 case COMPOSE_DND_ATTACH:
11068 val = G_ALERTOTHER;
11071 /* unexpected case */
11072 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11075 if (val & G_ALERTDISABLE) {
11076 val &= ~G_ALERTDISABLE;
11077 /* remember what action to perform by default, only if we don't click Cancel */
11078 if (val == G_ALERTALTERNATE)
11079 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11080 else if (val == G_ALERTOTHER)
11081 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11084 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11085 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11086 list_free_strings(list);
11089 } else if (val == G_ALERTOTHER) {
11090 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11091 list_free_strings(list);
11096 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11097 compose_insert_file(compose, (const gchar *)tmp->data);
11099 list_free_strings(list);
11101 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11106 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11109 static void compose_header_drag_received_cb (GtkWidget *widget,
11110 GdkDragContext *drag_context,
11113 GtkSelectionData *data,
11116 gpointer user_data)
11118 GtkEditable *entry = (GtkEditable *)user_data;
11119 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11121 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11124 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11125 gchar *decoded=g_new(gchar, strlen(email));
11128 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11129 gtk_editable_delete_text(entry, 0, -1);
11130 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11131 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11135 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11138 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11140 Compose *compose = (Compose *)data;
11142 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11143 compose->return_receipt = TRUE;
11145 compose->return_receipt = FALSE;
11148 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11150 Compose *compose = (Compose *)data;
11152 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11153 compose->remove_references = TRUE;
11155 compose->remove_references = FALSE;
11158 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11159 ComposeHeaderEntry *headerentry)
11161 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11165 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11166 GdkEventKey *event,
11167 ComposeHeaderEntry *headerentry)
11169 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11170 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11171 !(event->state & GDK_MODIFIER_MASK) &&
11172 (event->keyval == GDK_KEY_BackSpace) &&
11173 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11174 gtk_container_remove
11175 (GTK_CONTAINER(headerentry->compose->header_table),
11176 headerentry->combo);
11177 gtk_container_remove
11178 (GTK_CONTAINER(headerentry->compose->header_table),
11179 headerentry->entry);
11180 headerentry->compose->header_list =
11181 g_slist_remove(headerentry->compose->header_list,
11183 g_free(headerentry);
11184 } else if (event->keyval == GDK_KEY_Tab) {
11185 if (headerentry->compose->header_last == headerentry) {
11186 /* Override default next focus, and give it to subject_entry
11187 * instead of notebook tabs
11189 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11190 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11197 static gboolean scroll_postpone(gpointer data)
11199 Compose *compose = (Compose *)data;
11201 if (compose->batch)
11204 GTK_EVENTS_FLUSH();
11205 compose_show_first_last_header(compose, FALSE);
11209 static void compose_headerentry_changed_cb(GtkWidget *entry,
11210 ComposeHeaderEntry *headerentry)
11212 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11213 compose_create_header_entry(headerentry->compose);
11214 g_signal_handlers_disconnect_matched
11215 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11216 0, 0, NULL, NULL, headerentry);
11218 if (!headerentry->compose->batch)
11219 g_timeout_add(0, scroll_postpone, headerentry->compose);
11223 static gboolean compose_defer_auto_save_draft(Compose *compose)
11225 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11226 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11230 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11232 GtkAdjustment *vadj;
11234 cm_return_if_fail(compose);
11239 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11240 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11241 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11242 gtk_widget_get_parent(compose->header_table)));
11243 gtk_adjustment_set_value(vadj, (show_first ?
11244 gtk_adjustment_get_lower(vadj) :
11245 (gtk_adjustment_get_upper(vadj) -
11246 gtk_adjustment_get_page_size(vadj))));
11247 gtk_adjustment_changed(vadj);
11250 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11251 const gchar *text, gint len, Compose *compose)
11253 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11254 (G_OBJECT(compose->text), "paste_as_quotation"));
11257 cm_return_if_fail(text != NULL);
11259 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11260 G_CALLBACK(text_inserted),
11262 if (paste_as_quotation) {
11264 const gchar *qmark;
11266 GtkTextIter start_iter;
11269 len = strlen(text);
11271 new_text = g_strndup(text, len);
11273 qmark = compose_quote_char_from_context(compose);
11275 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11276 gtk_text_buffer_place_cursor(buffer, iter);
11278 pos = gtk_text_iter_get_offset(iter);
11280 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11281 _("Quote format error at line %d."));
11282 quote_fmt_reset_vartable();
11284 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11285 GINT_TO_POINTER(paste_as_quotation - 1));
11287 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11288 gtk_text_buffer_place_cursor(buffer, iter);
11289 gtk_text_buffer_delete_mark(buffer, mark);
11291 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11292 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11293 compose_beautify_paragraph(compose, &start_iter, FALSE);
11294 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11295 gtk_text_buffer_delete_mark(buffer, mark);
11297 if (strcmp(text, "\n") || compose->automatic_break
11298 || gtk_text_iter_starts_line(iter)) {
11299 GtkTextIter before_ins;
11300 gtk_text_buffer_insert(buffer, iter, text, len);
11301 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11302 before_ins = *iter;
11303 gtk_text_iter_backward_chars(&before_ins, len);
11304 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11307 /* check if the preceding is just whitespace or quote */
11308 GtkTextIter start_line;
11309 gchar *tmp = NULL, *quote = NULL;
11310 gint quote_len = 0, is_normal = 0;
11311 start_line = *iter;
11312 gtk_text_iter_set_line_offset(&start_line, 0);
11313 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11316 if (*tmp == '\0') {
11319 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11327 gtk_text_buffer_insert(buffer, iter, text, len);
11329 gtk_text_buffer_insert_with_tags_by_name(buffer,
11330 iter, text, len, "no_join", NULL);
11335 if (!paste_as_quotation) {
11336 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11337 compose_beautify_paragraph(compose, iter, FALSE);
11338 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11339 gtk_text_buffer_delete_mark(buffer, mark);
11342 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11343 G_CALLBACK(text_inserted),
11345 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11347 if (compose_can_autosave(compose) &&
11348 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11349 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11350 compose->draft_timeout_tag = g_timeout_add
11351 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11355 static void compose_check_all(GtkAction *action, gpointer data)
11357 Compose *compose = (Compose *)data;
11358 if (!compose->gtkaspell)
11361 if (gtk_widget_has_focus(compose->subject_entry))
11362 claws_spell_entry_check_all(
11363 CLAWS_SPELL_ENTRY(compose->subject_entry));
11365 gtkaspell_check_all(compose->gtkaspell);
11368 static void compose_highlight_all(GtkAction *action, gpointer data)
11370 Compose *compose = (Compose *)data;
11371 if (compose->gtkaspell) {
11372 claws_spell_entry_recheck_all(
11373 CLAWS_SPELL_ENTRY(compose->subject_entry));
11374 gtkaspell_highlight_all(compose->gtkaspell);
11378 static void compose_check_backwards(GtkAction *action, gpointer data)
11380 Compose *compose = (Compose *)data;
11381 if (!compose->gtkaspell) {
11382 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11386 if (gtk_widget_has_focus(compose->subject_entry))
11387 claws_spell_entry_check_backwards(
11388 CLAWS_SPELL_ENTRY(compose->subject_entry));
11390 gtkaspell_check_backwards(compose->gtkaspell);
11393 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11395 Compose *compose = (Compose *)data;
11396 if (!compose->gtkaspell) {
11397 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11401 if (gtk_widget_has_focus(compose->subject_entry))
11402 claws_spell_entry_check_forwards_go(
11403 CLAWS_SPELL_ENTRY(compose->subject_entry));
11405 gtkaspell_check_forwards_go(compose->gtkaspell);
11410 *\brief Guess originating forward account from MsgInfo and several
11411 * "common preference" settings. Return NULL if no guess.
11413 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11415 PrefsAccount *account = NULL;
11417 cm_return_val_if_fail(msginfo, NULL);
11418 cm_return_val_if_fail(msginfo->folder, NULL);
11419 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11421 if (msginfo->folder->prefs->enable_default_account)
11422 account = account_find_from_id(msginfo->folder->prefs->default_account);
11425 account = msginfo->folder->folder->account;
11427 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11429 Xstrdup_a(to, msginfo->to, return NULL);
11430 extract_address(to);
11431 account = account_find_from_address(to, FALSE);
11434 if (!account && prefs_common.forward_account_autosel) {
11435 gchar cc[BUFFSIZE];
11436 if (!procheader_get_header_from_msginfo
11437 (msginfo, cc,sizeof cc , "Cc:")) {
11438 gchar *buf = cc + strlen("Cc:");
11439 extract_address(buf);
11440 account = account_find_from_address(buf, FALSE);
11444 if (!account && prefs_common.forward_account_autosel) {
11445 gchar deliveredto[BUFFSIZE];
11446 if (!procheader_get_header_from_msginfo
11447 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11448 gchar *buf = deliveredto + strlen("Delivered-To:");
11449 extract_address(buf);
11450 account = account_find_from_address(buf, FALSE);
11457 gboolean compose_close(Compose *compose)
11461 cm_return_val_if_fail(compose, FALSE);
11463 if (!g_mutex_trylock(compose->mutex)) {
11464 /* we have to wait for the (possibly deferred by auto-save)
11465 * drafting to be done, before destroying the compose under
11467 debug_print("waiting for drafting to finish...\n");
11468 compose_allow_user_actions(compose, FALSE);
11469 if (compose->close_timeout_tag == 0) {
11470 compose->close_timeout_tag =
11471 g_timeout_add (500, (GSourceFunc) compose_close,
11477 if (compose->close_timeout_tag) {
11478 /* let the close be done by the deferred callback */
11479 g_mutex_unlock(compose->mutex);
11483 gtkut_widget_get_uposition(compose->window, &x, &y);
11484 if (!compose->batch) {
11485 prefs_common.compose_x = x;
11486 prefs_common.compose_y = y;
11488 g_mutex_unlock(compose->mutex);
11489 compose_destroy(compose);
11494 * Add entry field for each address in list.
11495 * \param compose E-Mail composition object.
11496 * \param listAddress List of (formatted) E-Mail addresses.
11498 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11501 node = listAddress;
11503 addr = ( gchar * ) node->data;
11504 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11505 node = g_list_next( node );
11509 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11510 guint action, gboolean opening_multiple)
11512 gchar *body = NULL;
11513 GSList *new_msglist = NULL;
11514 MsgInfo *tmp_msginfo = NULL;
11515 gboolean originally_enc = FALSE;
11516 gboolean originally_sig = FALSE;
11517 Compose *compose = NULL;
11518 gchar *s_system = NULL;
11520 cm_return_if_fail(msgview != NULL);
11522 cm_return_if_fail(msginfo_list != NULL);
11524 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11525 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11526 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11528 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11529 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11530 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11531 orig_msginfo, mimeinfo);
11532 if (tmp_msginfo != NULL) {
11533 new_msglist = g_slist_append(NULL, tmp_msginfo);
11535 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11536 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11537 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11539 tmp_msginfo->folder = orig_msginfo->folder;
11540 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11541 if (orig_msginfo->tags) {
11542 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11543 tmp_msginfo->folder->tags_dirty = TRUE;
11549 if (!opening_multiple)
11550 body = messageview_get_selection(msgview);
11553 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11554 procmsg_msginfo_free(tmp_msginfo);
11555 g_slist_free(new_msglist);
11557 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11559 if (compose && originally_enc) {
11560 compose_force_encryption(compose, compose->account, FALSE, s_system);
11563 if (compose && originally_sig && compose->account->default_sign_reply) {
11564 compose_force_signing(compose, compose->account, s_system);
11568 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11571 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11574 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11575 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11576 GSList *cur = msginfo_list;
11577 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11578 "messages. Opening the windows "
11579 "could take some time. Do you "
11580 "want to continue?"),
11581 g_slist_length(msginfo_list));
11582 if (g_slist_length(msginfo_list) > 9
11583 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11584 != G_ALERTALTERNATE) {
11589 /* We'll open multiple compose windows */
11590 /* let the WM place the next windows */
11591 compose_force_window_origin = FALSE;
11592 for (; cur; cur = cur->next) {
11594 tmplist.data = cur->data;
11595 tmplist.next = NULL;
11596 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11598 compose_force_window_origin = TRUE;
11600 /* forwarding multiple mails as attachments is done via a
11601 * single compose window */
11602 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11606 void compose_check_for_email_account(Compose *compose)
11608 PrefsAccount *ac = NULL, *curr = NULL;
11614 if (compose->account && compose->account->protocol == A_NNTP) {
11615 ac = account_get_cur_account();
11616 if (ac->protocol == A_NNTP) {
11617 list = account_get_list();
11619 for( ; list != NULL ; list = g_list_next(list)) {
11620 curr = (PrefsAccount *) list->data;
11621 if (curr->protocol != A_NNTP) {
11627 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11632 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11633 const gchar *address)
11635 GSList *msginfo_list = NULL;
11636 gchar *body = messageview_get_selection(msgview);
11639 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11641 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11642 compose_check_for_email_account(compose);
11643 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11644 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11645 compose_reply_set_subject(compose, msginfo);
11648 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11651 void compose_set_position(Compose *compose, gint pos)
11653 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11655 gtkut_text_view_set_position(text, pos);
11658 gboolean compose_search_string(Compose *compose,
11659 const gchar *str, gboolean case_sens)
11661 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11663 return gtkut_text_view_search_string(text, str, case_sens);
11666 gboolean compose_search_string_backward(Compose *compose,
11667 const gchar *str, gboolean case_sens)
11669 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11671 return gtkut_text_view_search_string_backward(text, str, case_sens);
11674 /* allocate a msginfo structure and populate its data from a compose data structure */
11675 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11677 MsgInfo *newmsginfo;
11679 gchar buf[BUFFSIZE];
11681 cm_return_val_if_fail( compose != NULL, NULL );
11683 newmsginfo = procmsg_msginfo_new();
11686 get_rfc822_date(buf, sizeof(buf));
11687 newmsginfo->date = g_strdup(buf);
11690 if (compose->from_name) {
11691 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11692 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11696 if (compose->subject_entry)
11697 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11699 /* to, cc, reply-to, newsgroups */
11700 for (list = compose->header_list; list; list = list->next) {
11701 gchar *header = gtk_editable_get_chars(
11703 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11704 gchar *entry = gtk_editable_get_chars(
11705 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11707 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11708 if ( newmsginfo->to == NULL ) {
11709 newmsginfo->to = g_strdup(entry);
11710 } else if (entry && *entry) {
11711 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11712 g_free(newmsginfo->to);
11713 newmsginfo->to = tmp;
11716 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11717 if ( newmsginfo->cc == NULL ) {
11718 newmsginfo->cc = g_strdup(entry);
11719 } else if (entry && *entry) {
11720 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11721 g_free(newmsginfo->cc);
11722 newmsginfo->cc = tmp;
11725 if ( strcasecmp(header,
11726 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11727 if ( newmsginfo->newsgroups == NULL ) {
11728 newmsginfo->newsgroups = g_strdup(entry);
11729 } else if (entry && *entry) {
11730 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11731 g_free(newmsginfo->newsgroups);
11732 newmsginfo->newsgroups = tmp;
11740 /* other data is unset */
11746 /* update compose's dictionaries from folder dict settings */
11747 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11748 FolderItem *folder_item)
11750 cm_return_if_fail(compose != NULL);
11752 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11753 FolderItemPrefs *prefs = folder_item->prefs;
11755 if (prefs->enable_default_dictionary)
11756 gtkaspell_change_dict(compose->gtkaspell,
11757 prefs->default_dictionary, FALSE);
11758 if (folder_item->prefs->enable_default_alt_dictionary)
11759 gtkaspell_change_alt_dict(compose->gtkaspell,
11760 prefs->default_alt_dictionary);
11761 if (prefs->enable_default_dictionary
11762 || prefs->enable_default_alt_dictionary)
11763 compose_spell_menu_changed(compose);
11768 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11770 Compose *compose = (Compose *)data;
11772 cm_return_if_fail(compose != NULL);
11774 gtk_widget_grab_focus(compose->text);