2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
186 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
188 static GList *compose_list = NULL;
189 static GSList *extra_headers = NULL;
191 static Compose *compose_generic_new (PrefsAccount *account,
195 GList *listAddress );
197 static Compose *compose_create (PrefsAccount *account,
202 static void compose_entry_mark_default_to (Compose *compose,
203 const gchar *address);
204 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
205 ComposeQuoteMode quote_mode,
209 static Compose *compose_forward_multiple (PrefsAccount *account,
210 GSList *msginfo_list);
211 static Compose *compose_reply (MsgInfo *msginfo,
212 ComposeQuoteMode quote_mode,
217 static Compose *compose_reply_mode (ComposeMode mode,
218 GSList *msginfo_list,
220 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
221 static void compose_update_privacy_systems_menu(Compose *compose);
223 static GtkWidget *compose_account_option_menu_create
225 static void compose_set_out_encoding (Compose *compose);
226 static void compose_set_template_menu (Compose *compose);
227 static void compose_destroy (Compose *compose);
229 static MailField compose_entries_set (Compose *compose,
231 ComposeEntryType to_type);
232 static gint compose_parse_header (Compose *compose,
234 static gint compose_parse_manual_headers (Compose *compose,
236 HeaderEntry *entries);
237 static gchar *compose_parse_references (const gchar *ref,
240 static gchar *compose_quote_fmt (Compose *compose,
246 gboolean need_unescape,
247 const gchar *err_msg);
249 static void compose_reply_set_entry (Compose *compose,
255 followup_and_reply_to);
256 static void compose_reedit_set_entry (Compose *compose,
259 static void compose_insert_sig (Compose *compose,
261 static ComposeInsertResult compose_insert_file (Compose *compose,
264 static gboolean compose_attach_append (Compose *compose,
267 const gchar *content_type,
268 const gchar *charset);
269 static void compose_attach_parts (Compose *compose,
272 static gboolean compose_beautify_paragraph (Compose *compose,
273 GtkTextIter *par_iter,
275 static void compose_wrap_all (Compose *compose);
276 static void compose_wrap_all_full (Compose *compose,
279 static void compose_set_title (Compose *compose);
280 static void compose_select_account (Compose *compose,
281 PrefsAccount *account,
284 static PrefsAccount *compose_current_mail_account(void);
285 /* static gint compose_send (Compose *compose); */
286 static gboolean compose_check_for_valid_recipient
288 static gboolean compose_check_entries (Compose *compose,
289 gboolean check_everything);
290 static gint compose_write_to_file (Compose *compose,
293 gboolean attach_parts);
294 static gint compose_write_body_to_file (Compose *compose,
296 static gint compose_remove_reedit_target (Compose *compose,
298 static void compose_remove_draft (Compose *compose);
299 static gint compose_queue_sub (Compose *compose,
303 gboolean check_subject,
304 gboolean remove_reedit_target);
305 static int compose_add_attachments (Compose *compose,
307 static gchar *compose_get_header (Compose *compose);
308 static gchar *compose_get_manual_headers_info (Compose *compose);
310 static void compose_convert_header (Compose *compose,
315 gboolean addr_field);
317 static void compose_attach_info_free (AttachInfo *ainfo);
318 static void compose_attach_remove_selected (GtkAction *action,
321 static void compose_template_apply (Compose *compose,
324 static void compose_attach_property (GtkAction *action,
326 static void compose_attach_property_create (gboolean *cancelled);
327 static void attach_property_ok (GtkWidget *widget,
328 gboolean *cancelled);
329 static void attach_property_cancel (GtkWidget *widget,
330 gboolean *cancelled);
331 static gint attach_property_delete_event (GtkWidget *widget,
333 gboolean *cancelled);
334 static gboolean attach_property_key_pressed (GtkWidget *widget,
336 gboolean *cancelled);
338 static void compose_exec_ext_editor (Compose *compose);
340 static gint compose_exec_ext_editor_real (const gchar *file);
341 static gboolean compose_ext_editor_kill (Compose *compose);
342 static gboolean compose_input_cb (GIOChannel *source,
343 GIOCondition condition,
345 static void compose_set_ext_editor_sensitive (Compose *compose,
347 #endif /* G_OS_UNIX */
349 static void compose_undo_state_changed (UndoMain *undostruct,
354 static void compose_create_header_entry (Compose *compose);
355 static void compose_add_header_entry (Compose *compose, const gchar *header,
356 gchar *text, ComposePrefType pref_type);
357 static void compose_remove_header_entries(Compose *compose);
359 static void compose_update_priority_menu_item(Compose * compose);
361 static void compose_spell_menu_changed (void *data);
362 static void compose_dict_changed (void *data);
364 static void compose_add_field_list ( Compose *compose,
365 GList *listAddress );
367 /* callback functions */
369 static void compose_notebook_size_alloc (GtkNotebook *notebook,
370 GtkAllocation *allocation,
372 static gboolean compose_edit_size_alloc (GtkEditable *widget,
373 GtkAllocation *allocation,
374 GtkSHRuler *shruler);
375 static void account_activated (GtkComboBox *optmenu,
377 static void attach_selected (GtkTreeView *tree_view,
378 GtkTreePath *tree_path,
379 GtkTreeViewColumn *column,
381 static gboolean attach_button_pressed (GtkWidget *widget,
382 GdkEventButton *event,
384 static gboolean attach_key_pressed (GtkWidget *widget,
387 static void compose_send_cb (GtkAction *action, gpointer data);
388 static void compose_send_later_cb (GtkAction *action, gpointer data);
390 static void compose_save_cb (GtkAction *action,
393 static void compose_attach_cb (GtkAction *action,
395 static void compose_insert_file_cb (GtkAction *action,
397 static void compose_insert_sig_cb (GtkAction *action,
399 static void compose_replace_sig_cb (GtkAction *action,
402 static void compose_close_cb (GtkAction *action,
404 static void compose_print_cb (GtkAction *action,
407 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
409 static void compose_address_cb (GtkAction *action,
411 static void about_show_cb (GtkAction *action,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (GtkAction *action,
419 static gint compose_delete_cb (GtkWidget *widget,
423 static void compose_undo_cb (GtkAction *action,
425 static void compose_redo_cb (GtkAction *action,
427 static void compose_cut_cb (GtkAction *action,
429 static void compose_copy_cb (GtkAction *action,
431 static void compose_paste_cb (GtkAction *action,
433 static void compose_paste_as_quote_cb (GtkAction *action,
435 static void compose_paste_no_wrap_cb (GtkAction *action,
437 static void compose_paste_wrap_cb (GtkAction *action,
439 static void compose_allsel_cb (GtkAction *action,
442 static void compose_advanced_action_cb (GtkAction *action,
445 static void compose_grab_focus_cb (GtkWidget *widget,
448 static void compose_changed_cb (GtkTextBuffer *textbuf,
451 static void compose_wrap_cb (GtkAction *action,
453 static void compose_wrap_all_cb (GtkAction *action,
455 static void compose_find_cb (GtkAction *action,
457 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
459 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
462 static void compose_toggle_ruler_cb (GtkToggleAction *action,
464 static void compose_toggle_sign_cb (GtkToggleAction *action,
466 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
468 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
469 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
470 static void activate_privacy_system (Compose *compose,
471 PrefsAccount *account,
473 static void compose_use_signing(Compose *compose, gboolean use_signing);
474 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
475 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
477 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
479 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
480 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
481 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
483 static void compose_attach_drag_received_cb (GtkWidget *widget,
484 GdkDragContext *drag_context,
487 GtkSelectionData *data,
491 static void compose_insert_drag_received_cb (GtkWidget *widget,
492 GdkDragContext *drag_context,
495 GtkSelectionData *data,
499 static void compose_header_drag_received_cb (GtkWidget *widget,
500 GdkDragContext *drag_context,
503 GtkSelectionData *data,
508 static gboolean compose_drag_drop (GtkWidget *widget,
509 GdkDragContext *drag_context,
511 guint time, gpointer user_data);
512 static gboolean completion_set_focus_to_subject
517 static void text_inserted (GtkTextBuffer *buffer,
522 static Compose *compose_generic_reply(MsgInfo *msginfo,
523 ComposeQuoteMode quote_mode,
527 gboolean followup_and_reply_to,
530 static void compose_headerentry_changed_cb (GtkWidget *entry,
531 ComposeHeaderEntry *headerentry);
532 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
534 ComposeHeaderEntry *headerentry);
535 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
536 ComposeHeaderEntry *headerentry);
538 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
540 static void compose_allow_user_actions (Compose *compose, gboolean allow);
542 static void compose_nothing_cb (GtkAction *action, gpointer data)
548 static void compose_check_all (GtkAction *action, gpointer data);
549 static void compose_highlight_all (GtkAction *action, gpointer data);
550 static void compose_check_backwards (GtkAction *action, gpointer data);
551 static void compose_check_forwards_go (GtkAction *action, gpointer data);
554 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
556 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
559 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
560 FolderItem *folder_item);
562 static void compose_attach_update_label(Compose *compose);
563 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
564 gboolean respect_default_to);
565 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
567 static GtkActionEntry compose_popup_entries[] =
569 {"Compose", NULL, "Compose" },
570 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
571 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
572 {"Compose/---", NULL, "---", NULL, NULL, NULL },
573 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
576 static GtkActionEntry compose_entries[] =
578 {"Menu", NULL, "Menu" },
580 {"Message", NULL, N_("_Message") },
581 {"Edit", NULL, N_("_Edit") },
583 {"Spelling", NULL, N_("_Spelling") },
585 {"Options", NULL, N_("_Options") },
586 {"Tools", NULL, N_("_Tools") },
587 {"Help", NULL, N_("_Help") },
589 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
590 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
591 {"Message/---", NULL, "---" },
593 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
594 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
595 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
596 {"Message/ReplaceSig", NULL, N_("_Replace signature"), NULL, NULL, G_CALLBACK(compose_replace_sig_cb) },
597 /* {"Message/---", NULL, "---" }, */
598 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
599 /* {"Message/---", NULL, "---" }, */
600 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
601 /* {"Message/---", NULL, "---" }, */
602 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
605 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
606 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
607 {"Edit/---", NULL, "---" },
609 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
610 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
611 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
613 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
614 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
615 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
616 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
618 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
620 {"Edit/Advanced", NULL, N_("A_dvanced") },
621 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
622 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
623 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
624 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
625 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
626 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
627 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
628 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
629 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
630 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
631 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
632 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
633 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
634 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
639 /* {"Edit/---", NULL, "---" }, */
640 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
641 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
642 /* {"Edit/---", NULL, "---" }, */
643 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
646 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
647 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
648 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
649 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
651 {"Spelling/---", NULL, "---" },
652 {"Spelling/Options", NULL, N_("_Options") },
657 {"Options/ReplyMode", NULL, N_("Reply _mode") },
658 {"Options/---", NULL, "---" },
659 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
660 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
662 /* {"Options/---", NULL, "---" }, */
664 {"Options/Priority", NULL, N_("_Priority") },
666 {"Options/Encoding", NULL, N_("Character _encoding") },
667 {"Options/Encoding/---", NULL, "---" },
668 #define ENC_ACTION(cs_char,c_char,string) \
669 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
671 {"Options/Encoding/Western", NULL, N_("Western European") },
672 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
673 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
674 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
675 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
676 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
677 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
678 {"Options/Encoding/Korean", NULL, N_("Korean") },
679 {"Options/Encoding/Thai", NULL, N_("Thai") },
682 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
684 {"Tools/Template", NULL, N_("_Template") },
685 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
686 {"Tools/Actions", NULL, N_("Actio_ns") },
687 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
690 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
693 static GtkToggleActionEntry compose_toggle_entries[] =
695 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
696 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
697 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
698 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
699 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
700 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
701 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
704 static GtkRadioActionEntry compose_radio_rm_entries[] =
706 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
707 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
708 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
709 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
712 static GtkRadioActionEntry compose_radio_prio_entries[] =
714 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
715 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
716 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
717 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
718 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
721 static GtkRadioActionEntry compose_radio_enc_entries[] =
723 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
757 static GtkTargetEntry compose_mime_types[] =
759 {"text/uri-list", 0, 0},
760 {"UTF8_STRING", 0, 0},
764 static gboolean compose_put_existing_to_front(MsgInfo *info)
766 const GList *compose_list = compose_get_compose_list();
767 const GList *elem = NULL;
770 for (elem = compose_list; elem != NULL && elem->data != NULL;
772 Compose *c = (Compose*)elem->data;
774 if (!c->targetinfo || !c->targetinfo->msgid ||
778 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
779 gtkut_window_popup(c->window);
787 static GdkColor quote_color1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_color2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_color3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor quote_bgcolor1 =
795 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
796 static GdkColor quote_bgcolor2 =
797 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
798 static GdkColor quote_bgcolor3 =
799 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
801 static GdkColor signature_color = {
808 static GdkColor uri_color = {
815 static void compose_create_tags(GtkTextView *text, Compose *compose)
817 GtkTextBuffer *buffer;
818 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
819 #if !GTK_CHECK_VERSION(2, 24, 0)
826 buffer = gtk_text_view_get_buffer(text);
828 if (prefs_common.enable_color) {
829 /* grab the quote colors, converting from an int to a GdkColor */
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
836 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
838 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
840 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
842 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
844 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
847 signature_color = quote_color1 = quote_color2 = quote_color3 =
848 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
851 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
852 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
853 "foreground-gdk", "e_color1,
854 "paragraph-background-gdk", "e_bgcolor1,
856 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
857 "foreground-gdk", "e_color2,
858 "paragraph-background-gdk", "e_bgcolor2,
860 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
861 "foreground-gdk", "e_color3,
862 "paragraph-background-gdk", "e_bgcolor3,
865 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
866 "foreground-gdk", "e_color1,
868 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
869 "foreground-gdk", "e_color2,
871 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
872 "foreground-gdk", "e_color3,
876 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
877 "foreground-gdk", &signature_color,
880 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
881 "foreground-gdk", &uri_color,
883 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
884 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
886 #if !GTK_CHECK_VERSION(2, 24, 0)
887 color[0] = quote_color1;
888 color[1] = quote_color2;
889 color[2] = quote_color3;
890 color[3] = quote_bgcolor1;
891 color[4] = quote_bgcolor2;
892 color[5] = quote_bgcolor3;
893 color[6] = signature_color;
894 color[7] = uri_color;
896 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
897 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
899 for (i = 0; i < 8; i++) {
900 if (success[i] == FALSE) {
901 g_warning("Compose: color allocation failed.\n");
902 quote_color1 = quote_color2 = quote_color3 =
903 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
904 signature_color = uri_color = black;
910 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
913 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
916 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
918 return compose_generic_new(account, mailto, item, NULL, NULL);
921 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
923 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
926 #define SCROLL_TO_CURSOR(compose) { \
927 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
928 gtk_text_view_get_buffer( \
929 GTK_TEXT_VIEW(compose->text))); \
930 gtk_text_view_scroll_mark_onscreen( \
931 GTK_TEXT_VIEW(compose->text), \
935 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
938 if (folderidentifier) {
939 #if !GTK_CHECK_VERSION(2, 24, 0)
940 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
942 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
944 prefs_common.compose_save_to_history = add_history(
945 prefs_common.compose_save_to_history, folderidentifier);
946 #if !GTK_CHECK_VERSION(2, 24, 0)
947 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
948 prefs_common.compose_save_to_history);
950 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
951 prefs_common.compose_save_to_history);
955 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
956 if (folderidentifier)
957 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
959 gtk_entry_set_text(GTK_ENTRY(entry), "");
962 static gchar *compose_get_save_to(Compose *compose)
965 gchar *result = NULL;
966 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
967 result = gtk_editable_get_chars(entry, 0, -1);
970 #if !GTK_CHECK_VERSION(2, 24, 0)
971 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
973 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
975 prefs_common.compose_save_to_history = add_history(
976 prefs_common.compose_save_to_history, result);
977 #if !GTK_CHECK_VERSION(2, 24, 0)
978 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
979 prefs_common.compose_save_to_history);
981 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
982 prefs_common.compose_save_to_history);
988 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
989 GList *attach_files, GList *listAddress )
992 GtkTextView *textview;
993 GtkTextBuffer *textbuf;
995 const gchar *subject_format = NULL;
996 const gchar *body_format = NULL;
997 gchar *mailto_from = NULL;
998 PrefsAccount *mailto_account = NULL;
999 MsgInfo* dummyinfo = NULL;
1000 gint cursor_pos = -1;
1001 MailField mfield = NO_FIELD_PRESENT;
1005 /* check if mailto defines a from */
1006 if (mailto && *mailto != '\0') {
1007 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1008 /* mailto defines a from, check if we can get account prefs from it,
1009 if not, the account prefs will be guessed using other ways, but we'll keep
1012 mailto_account = account_find_from_address(mailto_from, TRUE);
1013 if (mailto_account == NULL) {
1015 Xstrdup_a(tmp_from, mailto_from, return NULL);
1016 extract_address(tmp_from);
1017 mailto_account = account_find_from_address(tmp_from, TRUE);
1021 account = mailto_account;
1024 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1025 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1026 account = account_find_from_id(item->prefs->default_account);
1028 /* if no account prefs set, fallback to the current one */
1029 if (!account) account = cur_account;
1030 cm_return_val_if_fail(account != NULL, NULL);
1032 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1034 /* override from name if mailto asked for it */
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1037 g_free(mailto_from);
1039 /* override from name according to folder properties */
1040 if (item && item->prefs &&
1041 item->prefs->compose_with_format &&
1042 item->prefs->compose_override_from_format &&
1043 *item->prefs->compose_override_from_format != '\0') {
1048 dummyinfo = compose_msginfo_new_from_compose(compose);
1050 /* decode \-escape sequences in the internal representation of the quote format */
1051 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1052 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1055 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1056 compose->gtkaspell);
1058 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1060 quote_fmt_scan_string(tmp);
1063 buf = quote_fmt_get_buffer();
1065 alertpanel_error(_("New message From format error."));
1067 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1068 quote_fmt_reset_vartable();
1073 compose->replyinfo = NULL;
1074 compose->fwdinfo = NULL;
1076 textview = GTK_TEXT_VIEW(compose->text);
1077 textbuf = gtk_text_view_get_buffer(textview);
1078 compose_create_tags(textview, compose);
1080 undo_block(compose->undostruct);
1082 compose_set_dictionaries_from_folder_prefs(compose, item);
1085 if (account->auto_sig)
1086 compose_insert_sig(compose, FALSE);
1087 gtk_text_buffer_get_start_iter(textbuf, &iter);
1088 gtk_text_buffer_place_cursor(textbuf, &iter);
1090 if (account->protocol != A_NNTP) {
1091 if (mailto && *mailto != '\0') {
1092 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1095 compose_set_folder_prefs(compose, item, TRUE);
1097 if (item && item->ret_rcpt) {
1098 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1101 if (mailto && *mailto != '\0') {
1102 if (!strchr(mailto, '@'))
1103 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1105 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1106 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1107 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1108 mfield = TO_FIELD_PRESENT;
1111 * CLAWS: just don't allow return receipt request, even if the user
1112 * may want to send an email. simple but foolproof.
1114 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1116 compose_add_field_list( compose, listAddress );
1118 if (item && item->prefs && item->prefs->compose_with_format) {
1119 subject_format = item->prefs->compose_subject_format;
1120 body_format = item->prefs->compose_body_format;
1121 } else if (account->compose_with_format) {
1122 subject_format = account->compose_subject_format;
1123 body_format = account->compose_body_format;
1124 } else if (prefs_common.compose_with_format) {
1125 subject_format = prefs_common.compose_subject_format;
1126 body_format = prefs_common.compose_body_format;
1129 if (subject_format || body_format) {
1132 && *subject_format != '\0' )
1134 gchar *subject = NULL;
1139 dummyinfo = compose_msginfo_new_from_compose(compose);
1141 /* decode \-escape sequences in the internal representation of the quote format */
1142 tmp = g_malloc(strlen(subject_format)+1);
1143 pref_get_unescaped_pref(tmp, subject_format);
1145 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1147 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1148 compose->gtkaspell);
1150 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1152 quote_fmt_scan_string(tmp);
1155 buf = quote_fmt_get_buffer();
1157 alertpanel_error(_("New message subject format error."));
1159 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1160 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1161 quote_fmt_reset_vartable();
1165 mfield = SUBJECT_FIELD_PRESENT;
1169 && *body_format != '\0' )
1172 GtkTextBuffer *buffer;
1173 GtkTextIter start, end;
1177 dummyinfo = compose_msginfo_new_from_compose(compose);
1179 text = GTK_TEXT_VIEW(compose->text);
1180 buffer = gtk_text_view_get_buffer(text);
1181 gtk_text_buffer_get_start_iter(buffer, &start);
1182 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1183 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1185 compose_quote_fmt(compose, dummyinfo,
1187 NULL, tmp, FALSE, TRUE,
1188 _("The body of the \"New message\" template has an error at line %d."));
1189 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1190 quote_fmt_reset_vartable();
1194 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1195 gtkaspell_highlight_all(compose->gtkaspell);
1197 mfield = BODY_FIELD_PRESENT;
1201 procmsg_msginfo_free( dummyinfo );
1207 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1208 ainfo = (AttachInfo *) curr->data;
1209 compose_attach_append(compose, ainfo->file, ainfo->file,
1210 ainfo->content_type, ainfo->charset);
1214 compose_show_first_last_header(compose, TRUE);
1216 /* Set save folder */
1217 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1218 gchar *folderidentifier;
1220 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1221 folderidentifier = folder_item_get_identifier(item);
1222 compose_set_save_to(compose, folderidentifier);
1223 g_free(folderidentifier);
1226 /* Place cursor according to provided input (mfield) */
1228 case NO_FIELD_PRESENT:
1229 if (compose->header_last)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 case TO_FIELD_PRESENT:
1233 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1238 gtk_widget_grab_focus(compose->subject_entry);
1240 case SUBJECT_FIELD_PRESENT:
1241 textview = GTK_TEXT_VIEW(compose->text);
1244 textbuf = gtk_text_view_get_buffer(textview);
1247 mark = gtk_text_buffer_get_insert(textbuf);
1248 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1249 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1251 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1252 * only defers where it comes to the variable body
1253 * is not null. If no body is present compose->text
1254 * will be null in which case you cannot place the
1255 * cursor inside the component so. An empty component
1256 * is therefore created before placing the cursor
1258 case BODY_FIELD_PRESENT:
1259 cursor_pos = quote_fmt_get_cursor_pos();
1260 if (cursor_pos == -1)
1261 gtk_widget_grab_focus(compose->header_last->entry);
1263 gtk_widget_grab_focus(compose->text);
1267 undo_unblock(compose->undostruct);
1269 if (prefs_common.auto_exteditor)
1270 compose_exec_ext_editor(compose);
1272 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
1274 SCROLL_TO_CURSOR(compose);
1276 compose->modified = FALSE;
1277 compose_set_title(compose);
1279 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1284 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1285 gboolean override_pref, const gchar *system)
1287 const gchar *privacy = NULL;
1289 cm_return_if_fail(compose != NULL);
1290 cm_return_if_fail(account != NULL);
1292 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1295 if (account->default_privacy_system && strlen(account->default_privacy_system))
1296 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 else if (*(compose->privacy_system) == '\0') {
1313 g_free(compose->privacy_system);
1314 compose->privacy_system = g_strdup(privacy);
1316 compose_update_privacy_system_menu_item(compose, FALSE);
1317 compose_use_encryption(compose, TRUE);
1321 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1323 const gchar *privacy = NULL;
1325 if (account->default_privacy_system && strlen(account->default_privacy_system))
1326 privacy = account->default_privacy_system;
1330 GSList *privacy_avail = privacy_get_system_ids();
1331 if (privacy_avail && g_slist_length(privacy_avail)) {
1332 privacy = (gchar *)(privacy_avail->data);
1336 if (privacy != NULL) {
1338 g_free(compose->privacy_system);
1339 compose->privacy_system = NULL;
1341 if (compose->privacy_system == NULL)
1342 compose->privacy_system = g_strdup(privacy);
1343 compose_update_privacy_system_menu_item(compose, FALSE);
1344 compose_use_signing(compose, TRUE);
1348 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1352 Compose *compose = NULL;
1354 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1356 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1357 cm_return_val_if_fail(msginfo != NULL, NULL);
1359 list_len = g_slist_length(msginfo_list);
1363 case COMPOSE_REPLY_TO_ADDRESS:
1364 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1365 FALSE, prefs_common.default_reply_list, FALSE, body);
1367 case COMPOSE_REPLY_WITH_QUOTE:
1368 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1369 FALSE, prefs_common.default_reply_list, FALSE, body);
1371 case COMPOSE_REPLY_WITHOUT_QUOTE:
1372 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1373 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1375 case COMPOSE_REPLY_TO_SENDER:
1376 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1377 FALSE, FALSE, TRUE, body);
1379 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1380 compose = compose_followup_and_reply_to(msginfo,
1381 COMPOSE_QUOTE_CHECK,
1382 FALSE, FALSE, body);
1384 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1385 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1386 FALSE, FALSE, TRUE, body);
1388 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1389 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1390 FALSE, FALSE, TRUE, NULL);
1392 case COMPOSE_REPLY_TO_ALL:
1393 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1394 TRUE, FALSE, FALSE, body);
1396 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1397 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1398 TRUE, FALSE, FALSE, body);
1400 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1401 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1402 TRUE, FALSE, FALSE, NULL);
1404 case COMPOSE_REPLY_TO_LIST:
1405 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1406 FALSE, TRUE, FALSE, body);
1408 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1409 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1410 FALSE, TRUE, FALSE, body);
1412 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1413 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1414 FALSE, TRUE, FALSE, NULL);
1416 case COMPOSE_FORWARD:
1417 if (prefs_common.forward_as_attachment) {
1418 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1421 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1425 case COMPOSE_FORWARD_INLINE:
1426 /* check if we reply to more than one Message */
1427 if (list_len == 1) {
1428 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1431 /* more messages FALL THROUGH */
1432 case COMPOSE_FORWARD_AS_ATTACH:
1433 compose = compose_forward_multiple(NULL, msginfo_list);
1435 case COMPOSE_REDIRECT:
1436 compose = compose_redirect(NULL, msginfo, FALSE);
1439 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1442 if (compose == NULL) {
1443 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1447 compose->rmode = mode;
1448 switch (compose->rmode) {
1450 case COMPOSE_REPLY_WITH_QUOTE:
1451 case COMPOSE_REPLY_WITHOUT_QUOTE:
1452 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1453 debug_print("reply mode Normal\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1455 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1457 case COMPOSE_REPLY_TO_SENDER:
1458 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1459 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1460 debug_print("reply mode Sender\n");
1461 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1463 case COMPOSE_REPLY_TO_ALL:
1464 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1465 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1466 debug_print("reply mode All\n");
1467 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1469 case COMPOSE_REPLY_TO_LIST:
1470 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1471 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1472 debug_print("reply mode List\n");
1473 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1475 case COMPOSE_REPLY_TO_ADDRESS:
1476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1484 static Compose *compose_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1491 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1492 to_sender, FALSE, body);
1495 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1496 ComposeQuoteMode quote_mode,
1501 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1502 to_sender, TRUE, body);
1505 static void compose_extract_original_charset(Compose *compose)
1507 MsgInfo *info = NULL;
1508 if (compose->replyinfo) {
1509 info = compose->replyinfo;
1510 } else if (compose->fwdinfo) {
1511 info = compose->fwdinfo;
1512 } else if (compose->targetinfo) {
1513 info = compose->targetinfo;
1516 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1517 MimeInfo *partinfo = mimeinfo;
1518 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1519 partinfo = procmime_mimeinfo_next(partinfo);
1521 compose->orig_charset =
1522 g_strdup(procmime_mimeinfo_get_parameter(
1523 partinfo, "charset"));
1525 procmime_mimeinfo_free_all(mimeinfo);
1529 #define SIGNAL_BLOCK(buffer) { \
1530 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1531 G_CALLBACK(compose_changed_cb), \
1533 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1534 G_CALLBACK(text_inserted), \
1538 #define SIGNAL_UNBLOCK(buffer) { \
1539 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1540 G_CALLBACK(compose_changed_cb), \
1542 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(text_inserted), \
1547 static Compose *compose_generic_reply(MsgInfo *msginfo,
1548 ComposeQuoteMode quote_mode,
1549 gboolean to_all, gboolean to_ml,
1551 gboolean followup_and_reply_to,
1555 PrefsAccount *account = NULL;
1556 GtkTextView *textview;
1557 GtkTextBuffer *textbuf;
1558 gboolean quote = FALSE;
1559 const gchar *qmark = NULL;
1560 const gchar *body_fmt = NULL;
1561 gchar *s_system = NULL;
1563 cm_return_val_if_fail(msginfo != NULL, NULL);
1564 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1566 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1568 cm_return_val_if_fail(account != NULL, NULL);
1570 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1572 compose->updating = TRUE;
1574 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1577 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1578 if (!compose->replyinfo)
1579 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1581 compose_extract_original_charset(compose);
1583 if (msginfo->folder && msginfo->folder->ret_rcpt)
1584 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1586 /* Set save folder */
1587 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1588 gchar *folderidentifier;
1590 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1591 folderidentifier = folder_item_get_identifier(msginfo->folder);
1592 compose_set_save_to(compose, folderidentifier);
1593 g_free(folderidentifier);
1596 if (compose_parse_header(compose, msginfo) < 0) {
1597 compose->updating = FALSE;
1598 compose_destroy(compose);
1602 /* override from name according to folder properties */
1603 if (msginfo->folder && msginfo->folder->prefs &&
1604 msginfo->folder->prefs->reply_with_format &&
1605 msginfo->folder->prefs->reply_override_from_format &&
1606 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1611 /* decode \-escape sequences in the internal representation of the quote format */
1612 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1613 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1616 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1617 compose->gtkaspell);
1619 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1621 quote_fmt_scan_string(tmp);
1624 buf = quote_fmt_get_buffer();
1626 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1628 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1629 quote_fmt_reset_vartable();
1634 textview = (GTK_TEXT_VIEW(compose->text));
1635 textbuf = gtk_text_view_get_buffer(textview);
1636 compose_create_tags(textview, compose);
1638 undo_block(compose->undostruct);
1640 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1641 gtkaspell_block_check(compose->gtkaspell);
1644 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1645 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1646 /* use the reply format of folder (if enabled), or the account's one
1647 (if enabled) or fallback to the global reply format, which is always
1648 enabled (even if empty), and use the relevant quotemark */
1650 if (msginfo->folder && msginfo->folder->prefs &&
1651 msginfo->folder->prefs->reply_with_format) {
1652 qmark = msginfo->folder->prefs->reply_quotemark;
1653 body_fmt = msginfo->folder->prefs->reply_body_format;
1655 } else if (account->reply_with_format) {
1656 qmark = account->reply_quotemark;
1657 body_fmt = account->reply_body_format;
1660 qmark = prefs_common.quotemark;
1661 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1662 body_fmt = gettext(prefs_common.quotefmt);
1669 /* empty quotemark is not allowed */
1670 if (qmark == NULL || *qmark == '\0')
1672 compose_quote_fmt(compose, compose->replyinfo,
1673 body_fmt, qmark, body, FALSE, TRUE,
1674 _("The body of the \"Reply\" template has an error at line %d."));
1675 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1676 quote_fmt_reset_vartable();
1679 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1680 compose_force_encryption(compose, account, FALSE, s_system);
1683 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1684 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1685 compose_force_signing(compose, account, s_system);
1689 SIGNAL_BLOCK(textbuf);
1691 if (account->auto_sig)
1692 compose_insert_sig(compose, FALSE);
1694 compose_wrap_all(compose);
1697 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1698 gtkaspell_highlight_all(compose->gtkaspell);
1699 gtkaspell_unblock_check(compose->gtkaspell);
1701 SIGNAL_UNBLOCK(textbuf);
1703 gtk_widget_grab_focus(compose->text);
1705 undo_unblock(compose->undostruct);
1707 if (prefs_common.auto_exteditor)
1708 compose_exec_ext_editor(compose);
1710 compose->modified = FALSE;
1711 compose_set_title(compose);
1713 compose->updating = FALSE;
1714 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1715 SCROLL_TO_CURSOR(compose);
1717 if (compose->deferred_destroy) {
1718 compose_destroy(compose);
1726 #define INSERT_FW_HEADER(var, hdr) \
1727 if (msginfo->var && *msginfo->var) { \
1728 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1729 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1730 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1733 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1734 gboolean as_attach, const gchar *body,
1735 gboolean no_extedit,
1739 GtkTextView *textview;
1740 GtkTextBuffer *textbuf;
1741 gint cursor_pos = -1;
1744 cm_return_val_if_fail(msginfo != NULL, NULL);
1745 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1748 !(account = compose_guess_forward_account_from_msginfo
1750 account = cur_account;
1752 if (!prefs_common.forward_as_attachment)
1753 mode = COMPOSE_FORWARD_INLINE;
1755 mode = COMPOSE_FORWARD;
1756 compose = compose_create(account, msginfo->folder, mode, batch);
1758 compose->updating = TRUE;
1759 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1760 if (!compose->fwdinfo)
1761 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1763 compose_extract_original_charset(compose);
1765 if (msginfo->subject && *msginfo->subject) {
1766 gchar *buf, *buf2, *p;
1768 buf = p = g_strdup(msginfo->subject);
1769 p += subject_get_prefix_length(p);
1770 memmove(buf, p, strlen(p) + 1);
1772 buf2 = g_strdup_printf("Fw: %s", buf);
1773 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1779 /* override from name according to folder properties */
1780 if (msginfo->folder && msginfo->folder->prefs &&
1781 msginfo->folder->prefs->forward_with_format &&
1782 msginfo->folder->prefs->forward_override_from_format &&
1783 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1787 MsgInfo *full_msginfo = NULL;
1790 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1792 full_msginfo = procmsg_msginfo_copy(msginfo);
1794 /* decode \-escape sequences in the internal representation of the quote format */
1795 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1796 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1799 gtkaspell_block_check(compose->gtkaspell);
1800 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1801 compose->gtkaspell);
1803 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1805 quote_fmt_scan_string(tmp);
1808 buf = quote_fmt_get_buffer();
1810 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1812 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1813 quote_fmt_reset_vartable();
1816 procmsg_msginfo_free(full_msginfo);
1819 textview = GTK_TEXT_VIEW(compose->text);
1820 textbuf = gtk_text_view_get_buffer(textview);
1821 compose_create_tags(textview, compose);
1823 undo_block(compose->undostruct);
1827 msgfile = procmsg_get_message_file(msginfo);
1828 if (!is_file_exist(msgfile))
1829 g_warning("%s: file not exist\n", msgfile);
1831 compose_attach_append(compose, msgfile, msgfile,
1832 "message/rfc822", NULL);
1836 const gchar *qmark = NULL;
1837 const gchar *body_fmt = NULL;
1838 MsgInfo *full_msginfo;
1840 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1842 full_msginfo = procmsg_msginfo_copy(msginfo);
1844 /* use the forward format of folder (if enabled), or the account's one
1845 (if enabled) or fallback to the global forward format, which is always
1846 enabled (even if empty), and use the relevant quotemark */
1847 if (msginfo->folder && msginfo->folder->prefs &&
1848 msginfo->folder->prefs->forward_with_format) {
1849 qmark = msginfo->folder->prefs->forward_quotemark;
1850 body_fmt = msginfo->folder->prefs->forward_body_format;
1852 } else if (account->forward_with_format) {
1853 qmark = account->forward_quotemark;
1854 body_fmt = account->forward_body_format;
1857 qmark = prefs_common.fw_quotemark;
1858 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1859 body_fmt = gettext(prefs_common.fw_quotefmt);
1864 /* empty quotemark is not allowed */
1865 if (qmark == NULL || *qmark == '\0')
1868 compose_quote_fmt(compose, full_msginfo,
1869 body_fmt, qmark, body, FALSE, TRUE,
1870 _("The body of the \"Forward\" template has an error at line %d."));
1871 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1872 quote_fmt_reset_vartable();
1873 compose_attach_parts(compose, msginfo);
1875 procmsg_msginfo_free(full_msginfo);
1878 SIGNAL_BLOCK(textbuf);
1880 if (account->auto_sig)
1881 compose_insert_sig(compose, FALSE);
1883 compose_wrap_all(compose);
1886 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1887 gtkaspell_highlight_all(compose->gtkaspell);
1888 gtkaspell_unblock_check(compose->gtkaspell);
1890 SIGNAL_UNBLOCK(textbuf);
1892 cursor_pos = quote_fmt_get_cursor_pos();
1893 if (cursor_pos == -1)
1894 gtk_widget_grab_focus(compose->header_last->entry);
1896 gtk_widget_grab_focus(compose->text);
1898 if (!no_extedit && prefs_common.auto_exteditor)
1899 compose_exec_ext_editor(compose);
1902 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1903 gchar *folderidentifier;
1905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1906 folderidentifier = folder_item_get_identifier(msginfo->folder);
1907 compose_set_save_to(compose, folderidentifier);
1908 g_free(folderidentifier);
1911 undo_unblock(compose->undostruct);
1913 compose->modified = FALSE;
1914 compose_set_title(compose);
1916 compose->updating = FALSE;
1917 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
1918 SCROLL_TO_CURSOR(compose);
1920 if (compose->deferred_destroy) {
1921 compose_destroy(compose);
1925 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1930 #undef INSERT_FW_HEADER
1932 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1935 GtkTextView *textview;
1936 GtkTextBuffer *textbuf;
1940 gboolean single_mail = TRUE;
1942 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1944 if (g_slist_length(msginfo_list) > 1)
1945 single_mail = FALSE;
1947 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1948 if (((MsgInfo *)msginfo->data)->folder == NULL)
1951 /* guess account from first selected message */
1953 !(account = compose_guess_forward_account_from_msginfo
1954 (msginfo_list->data)))
1955 account = cur_account;
1957 cm_return_val_if_fail(account != NULL, NULL);
1959 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1960 if (msginfo->data) {
1961 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1962 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1966 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1967 g_warning("no msginfo_list");
1971 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1973 compose->updating = TRUE;
1975 /* override from name according to folder properties */
1976 if (msginfo_list->data) {
1977 MsgInfo *msginfo = msginfo_list->data;
1979 if (msginfo->folder && msginfo->folder->prefs &&
1980 msginfo->folder->prefs->forward_with_format &&
1981 msginfo->folder->prefs->forward_override_from_format &&
1982 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1987 /* decode \-escape sequences in the internal representation of the quote format */
1988 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1989 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1992 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1993 compose->gtkaspell);
1995 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1997 quote_fmt_scan_string(tmp);
2000 buf = quote_fmt_get_buffer();
2002 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
2005 quote_fmt_reset_vartable();
2011 textview = GTK_TEXT_VIEW(compose->text);
2012 textbuf = gtk_text_view_get_buffer(textview);
2013 compose_create_tags(textview, compose);
2015 undo_block(compose->undostruct);
2016 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2017 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2019 if (!is_file_exist(msgfile))
2020 g_warning("%s: file not exist\n", msgfile);
2022 compose_attach_append(compose, msgfile, msgfile,
2023 "message/rfc822", NULL);
2028 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2029 if (info->subject && *info->subject) {
2030 gchar *buf, *buf2, *p;
2032 buf = p = g_strdup(info->subject);
2033 p += subject_get_prefix_length(p);
2034 memmove(buf, p, strlen(p) + 1);
2036 buf2 = g_strdup_printf("Fw: %s", buf);
2037 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2043 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2044 _("Fw: multiple emails"));
2047 SIGNAL_BLOCK(textbuf);
2049 if (account->auto_sig)
2050 compose_insert_sig(compose, FALSE);
2052 compose_wrap_all(compose);
2054 SIGNAL_UNBLOCK(textbuf);
2056 gtk_text_buffer_get_start_iter(textbuf, &iter);
2057 gtk_text_buffer_place_cursor(textbuf, &iter);
2059 gtk_widget_grab_focus(compose->header_last->entry);
2060 undo_unblock(compose->undostruct);
2061 compose->modified = FALSE;
2062 compose_set_title(compose);
2064 compose->updating = FALSE;
2065 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2066 SCROLL_TO_CURSOR(compose);
2068 if (compose->deferred_destroy) {
2069 compose_destroy(compose);
2073 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2078 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2080 GtkTextIter start = *iter;
2081 GtkTextIter end_iter;
2082 int start_pos = gtk_text_iter_get_offset(&start);
2084 if (!compose->account->sig_sep)
2087 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2088 start_pos+strlen(compose->account->sig_sep));
2090 /* check sig separator */
2091 str = gtk_text_iter_get_text(&start, &end_iter);
2092 if (!strcmp(str, compose->account->sig_sep)) {
2094 /* check end of line (\n) */
2095 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2096 start_pos+strlen(compose->account->sig_sep));
2097 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2098 start_pos+strlen(compose->account->sig_sep)+1);
2099 tmp = gtk_text_iter_get_text(&start, &end_iter);
2100 if (!strcmp(tmp,"\n")) {
2112 static gboolean compose_update_folder_hook(gpointer source, gpointer data)
2114 FolderUpdateData *hookdata = (FolderUpdateData *)source;
2115 Compose *compose = (Compose *)data;
2116 FolderItem *old_item = NULL;
2117 FolderItem *new_item = NULL;
2118 gchar *old_id, *new_id;
2120 if (!(hookdata->update_flags & FOLDER_REMOVE_FOLDERITEM)
2121 && !(hookdata->update_flags & FOLDER_MOVE_FOLDERITEM))
2124 old_item = hookdata->item;
2125 new_item = hookdata->item2;
2127 old_id = folder_item_get_identifier(old_item);
2128 new_id = new_item ? folder_item_get_identifier(new_item) : g_strdup("NULL");
2130 if (compose->targetinfo && compose->targetinfo->folder == old_item) {
2131 debug_print("updating targetinfo folder: %s -> %s\n", old_id, new_id);
2132 compose->targetinfo->folder = new_item;
2135 if (compose->replyinfo && compose->replyinfo->folder == old_item) {
2136 debug_print("updating replyinfo folder: %s -> %s\n", old_id, new_id);
2137 compose->replyinfo->folder = new_item;
2140 if (compose->fwdinfo && compose->fwdinfo->folder == old_item) {
2141 debug_print("updating fwdinfo folder: %s -> %s\n", old_id, new_id);
2142 compose->fwdinfo->folder = new_item;
2150 static void compose_colorize_signature(Compose *compose)
2152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2154 GtkTextIter end_iter;
2155 gtk_text_buffer_get_start_iter(buffer, &iter);
2156 while (gtk_text_iter_forward_line(&iter))
2157 if (compose_is_sig_separator(compose, buffer, &iter)) {
2158 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2159 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2163 #define BLOCK_WRAP() { \
2164 prev_autowrap = compose->autowrap; \
2165 buffer = gtk_text_view_get_buffer( \
2166 GTK_TEXT_VIEW(compose->text)); \
2167 compose->autowrap = FALSE; \
2169 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2170 G_CALLBACK(compose_changed_cb), \
2172 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2173 G_CALLBACK(text_inserted), \
2176 #define UNBLOCK_WRAP() { \
2177 compose->autowrap = prev_autowrap; \
2178 if (compose->autowrap) { \
2179 gint old = compose->draft_timeout_tag; \
2180 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2181 compose_wrap_all(compose); \
2182 compose->draft_timeout_tag = old; \
2185 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2186 G_CALLBACK(compose_changed_cb), \
2188 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2189 G_CALLBACK(text_inserted), \
2193 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2195 Compose *compose = NULL;
2196 PrefsAccount *account = NULL;
2197 GtkTextView *textview;
2198 GtkTextBuffer *textbuf;
2202 gchar buf[BUFFSIZE];
2203 gboolean use_signing = FALSE;
2204 gboolean use_encryption = FALSE;
2205 gchar *privacy_system = NULL;
2206 int priority = PRIORITY_NORMAL;
2207 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2208 gboolean autowrap = prefs_common.autowrap;
2209 gboolean autoindent = prefs_common.auto_indent;
2210 HeaderEntry *manual_headers = NULL;
2212 cm_return_val_if_fail(msginfo != NULL, NULL);
2213 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2215 if (compose_put_existing_to_front(msginfo)) {
2219 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2220 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2221 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2222 gchar queueheader_buf[BUFFSIZE];
2225 /* Select Account from queue headers */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2228 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2229 account = account_find_from_id(id);
2231 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2232 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2233 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2234 account = account_find_from_id(id);
2236 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2237 sizeof(queueheader_buf), "NAID:")) {
2238 id = atoi(&queueheader_buf[strlen("NAID:")]);
2239 account = account_find_from_id(id);
2241 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "MAID:")) {
2243 id = atoi(&queueheader_buf[strlen("MAID:")]);
2244 account = account_find_from_id(id);
2246 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2247 sizeof(queueheader_buf), "S:")) {
2248 account = account_find_from_address(queueheader_buf, FALSE);
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2252 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2253 use_signing = param;
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2257 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2258 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2259 use_signing = param;
2262 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2263 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2264 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2265 use_encryption = param;
2267 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2268 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2269 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2270 use_encryption = param;
2272 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2273 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2274 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2277 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2278 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2279 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2282 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2283 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2284 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2286 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2287 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2288 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2290 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2291 sizeof(queueheader_buf), "X-Priority: ")) {
2292 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2295 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2296 sizeof(queueheader_buf), "RMID:")) {
2297 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2298 if (tokens[0] && tokens[1] && tokens[2]) {
2299 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2300 if (orig_item != NULL) {
2301 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2306 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2307 sizeof(queueheader_buf), "FMID:")) {
2308 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2309 if (tokens[0] && tokens[1] && tokens[2]) {
2310 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2311 if (orig_item != NULL) {
2312 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2317 /* Get manual headers */
2318 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2319 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2320 if (*listmh != '\0') {
2321 debug_print("Got manual headers: %s\n", listmh);
2322 manual_headers = procheader_entries_from_str(listmh);
2327 account = msginfo->folder->folder->account;
2330 if (!account && prefs_common.reedit_account_autosel) {
2331 gchar from[BUFFSIZE];
2332 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2333 extract_address(from);
2334 account = account_find_from_address(from, FALSE);
2338 account = cur_account;
2340 cm_return_val_if_fail(account != NULL, NULL);
2342 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2344 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2345 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2346 compose->autowrap = autowrap;
2347 compose->replyinfo = replyinfo;
2348 compose->fwdinfo = fwdinfo;
2350 compose->updating = TRUE;
2351 compose->priority = priority;
2353 if (privacy_system != NULL) {
2354 compose->privacy_system = privacy_system;
2355 compose_use_signing(compose, use_signing);
2356 compose_use_encryption(compose, use_encryption);
2357 compose_update_privacy_system_menu_item(compose, FALSE);
2359 activate_privacy_system(compose, account, FALSE);
2362 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2364 compose_extract_original_charset(compose);
2366 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2367 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2368 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2369 gchar queueheader_buf[BUFFSIZE];
2371 /* Set message save folder */
2372 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2373 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2374 compose_set_save_to(compose, &queueheader_buf[4]);
2376 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2377 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2379 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2384 if (compose_parse_header(compose, msginfo) < 0) {
2385 compose->updating = FALSE;
2386 compose_destroy(compose);
2389 compose_reedit_set_entry(compose, msginfo);
2391 textview = GTK_TEXT_VIEW(compose->text);
2392 textbuf = gtk_text_view_get_buffer(textview);
2393 compose_create_tags(textview, compose);
2395 mark = gtk_text_buffer_get_insert(textbuf);
2396 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2398 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2399 G_CALLBACK(compose_changed_cb),
2402 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2403 fp = procmime_get_first_encrypted_text_content(msginfo);
2405 compose_force_encryption(compose, account, TRUE, NULL);
2408 fp = procmime_get_first_text_content(msginfo);
2411 g_warning("Can't get text part\n");
2415 gboolean prev_autowrap;
2416 GtkTextBuffer *buffer;
2418 while (fgets(buf, sizeof(buf), fp) != NULL) {
2420 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2426 compose_attach_parts(compose, msginfo);
2428 compose_colorize_signature(compose);
2430 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2431 G_CALLBACK(compose_changed_cb),
2434 if (manual_headers != NULL) {
2435 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2436 procheader_entries_free(manual_headers);
2437 compose->updating = FALSE;
2438 compose_destroy(compose);
2441 procheader_entries_free(manual_headers);
2444 gtk_widget_grab_focus(compose->text);
2446 if (prefs_common.auto_exteditor) {
2447 compose_exec_ext_editor(compose);
2449 compose->modified = FALSE;
2450 compose_set_title(compose);
2452 compose->updating = FALSE;
2453 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2454 SCROLL_TO_CURSOR(compose);
2456 if (compose->deferred_destroy) {
2457 compose_destroy(compose);
2461 compose->sig_str = account_get_signature_str(compose->account);
2463 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2468 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2475 cm_return_val_if_fail(msginfo != NULL, NULL);
2478 account = account_get_reply_account(msginfo,
2479 prefs_common.reply_account_autosel);
2480 cm_return_val_if_fail(account != NULL, NULL);
2482 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2484 compose->updating = TRUE;
2486 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2487 compose->replyinfo = NULL;
2488 compose->fwdinfo = NULL;
2490 compose_show_first_last_header(compose, TRUE);
2492 gtk_widget_grab_focus(compose->header_last->entry);
2494 filename = procmsg_get_message_file(msginfo);
2496 if (filename == NULL) {
2497 compose->updating = FALSE;
2498 compose_destroy(compose);
2503 compose->redirect_filename = filename;
2505 /* Set save folder */
2506 item = msginfo->folder;
2507 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2508 gchar *folderidentifier;
2510 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2511 folderidentifier = folder_item_get_identifier(item);
2512 compose_set_save_to(compose, folderidentifier);
2513 g_free(folderidentifier);
2516 compose_attach_parts(compose, msginfo);
2518 if (msginfo->subject)
2519 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2521 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2523 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2524 _("The body of the \"Redirect\" template has an error at line %d."));
2525 quote_fmt_reset_vartable();
2526 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2528 compose_colorize_signature(compose);
2531 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2532 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2533 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", FALSE);
2540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2542 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2545 if (compose->toolbar->draft_btn)
2546 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2547 if (compose->toolbar->insert_btn)
2548 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2549 if (compose->toolbar->attach_btn)
2550 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2551 if (compose->toolbar->sig_btn)
2552 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2553 if (compose->toolbar->exteditor_btn)
2554 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2555 if (compose->toolbar->linewrap_current_btn)
2556 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2557 if (compose->toolbar->linewrap_all_btn)
2558 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2560 compose->modified = FALSE;
2561 compose_set_title(compose);
2562 compose->updating = FALSE;
2563 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
2564 SCROLL_TO_CURSOR(compose);
2566 if (compose->deferred_destroy) {
2567 compose_destroy(compose);
2571 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2576 const GList *compose_get_compose_list(void)
2578 return compose_list;
2581 void compose_entry_append(Compose *compose, const gchar *address,
2582 ComposeEntryType type, ComposePrefType pref_type)
2584 const gchar *header;
2586 gboolean in_quote = FALSE;
2587 if (!address || *address == '\0') return;
2594 header = N_("Bcc:");
2596 case COMPOSE_REPLYTO:
2597 header = N_("Reply-To:");
2599 case COMPOSE_NEWSGROUPS:
2600 header = N_("Newsgroups:");
2602 case COMPOSE_FOLLOWUPTO:
2603 header = N_( "Followup-To:");
2605 case COMPOSE_INREPLYTO:
2606 header = N_( "In-Reply-To:");
2613 header = prefs_common_translated_header_name(header);
2615 cur = begin = (gchar *)address;
2617 /* we separate the line by commas, but not if we're inside a quoted
2619 while (*cur != '\0') {
2621 in_quote = !in_quote;
2622 if (*cur == ',' && !in_quote) {
2623 gchar *tmp = g_strdup(begin);
2625 tmp[cur-begin]='\0';
2628 while (*tmp == ' ' || *tmp == '\t')
2630 compose_add_header_entry(compose, header, tmp, pref_type);
2637 gchar *tmp = g_strdup(begin);
2639 tmp[cur-begin]='\0';
2640 while (*tmp == ' ' || *tmp == '\t')
2642 compose_add_header_entry(compose, header, tmp, pref_type);
2647 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2649 #if !GTK_CHECK_VERSION(3, 0, 0)
2650 static GdkColor yellow;
2651 static GdkColor black;
2652 static gboolean yellow_initialised = FALSE;
2654 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2655 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2660 #if !GTK_CHECK_VERSION(3, 0, 0)
2661 if (!yellow_initialised) {
2662 gdk_color_parse("#f5f6be", &yellow);
2663 gdk_color_parse("#000000", &black);
2664 yellow_initialised = gdk_colormap_alloc_color(
2665 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2666 yellow_initialised &= gdk_colormap_alloc_color(
2667 gdk_colormap_get_system(), &black, FALSE, TRUE);
2671 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2672 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2673 if (gtk_entry_get_text(entry) &&
2674 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (yellow_initialised) {
2678 gtk_widget_modify_base(
2679 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2680 GTK_STATE_NORMAL, &yellow);
2681 gtk_widget_modify_text(
2682 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2683 GTK_STATE_NORMAL, &black);
2684 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 void compose_toolbar_cb(gint action, gpointer data)
2693 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2694 Compose *compose = (Compose*)toolbar_item->parent;
2696 cm_return_if_fail(compose != NULL);
2700 compose_send_cb(NULL, compose);
2703 compose_send_later_cb(NULL, compose);
2706 compose_draft(compose, COMPOSE_QUIT_EDITING);
2709 compose_insert_file_cb(NULL, compose);
2712 compose_attach_cb(NULL, compose);
2715 compose_insert_sig(compose, FALSE);
2718 compose_insert_sig(compose, TRUE);
2721 compose_ext_editor_cb(NULL, compose);
2723 case A_LINEWRAP_CURRENT:
2724 compose_beautify_paragraph(compose, NULL, TRUE);
2726 case A_LINEWRAP_ALL:
2727 compose_wrap_all_full(compose, TRUE);
2730 compose_address_cb(NULL, compose);
2733 case A_CHECK_SPELLING:
2734 compose_check_all(NULL, compose);
2742 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2747 gchar *subject = NULL;
2751 gchar **attach = NULL;
2752 gchar *inreplyto = NULL;
2753 MailField mfield = NO_FIELD_PRESENT;
2755 /* get mailto parts but skip from */
2756 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2759 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2760 mfield = TO_FIELD_PRESENT;
2763 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2765 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2767 if (!g_utf8_validate (subject, -1, NULL)) {
2768 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2769 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2772 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2774 mfield = SUBJECT_FIELD_PRESENT;
2777 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2778 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2781 gboolean prev_autowrap = compose->autowrap;
2783 compose->autowrap = FALSE;
2785 mark = gtk_text_buffer_get_insert(buffer);
2786 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2788 if (!g_utf8_validate (body, -1, NULL)) {
2789 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2790 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2793 gtk_text_buffer_insert(buffer, &iter, body, -1);
2795 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2797 compose->autowrap = prev_autowrap;
2798 if (compose->autowrap)
2799 compose_wrap_all(compose);
2800 mfield = BODY_FIELD_PRESENT;
2804 gint i = 0, att = 0;
2805 gchar *warn_files = NULL;
2806 while (attach[i] != NULL) {
2807 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2808 if (utf8_filename) {
2809 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2810 gchar *tmp = g_strdup_printf("%s%s\n",
2811 warn_files?warn_files:"",
2817 g_free(utf8_filename);
2819 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2824 alertpanel_notice(ngettext(
2825 "The following file has been attached: \n%s",
2826 "The following files have been attached: \n%s", att), warn_files);
2831 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2844 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2846 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2847 {"Cc:", NULL, TRUE},
2848 {"References:", NULL, FALSE},
2849 {"Bcc:", NULL, TRUE},
2850 {"Newsgroups:", NULL, TRUE},
2851 {"Followup-To:", NULL, TRUE},
2852 {"List-Post:", NULL, FALSE},
2853 {"X-Priority:", NULL, FALSE},
2854 {NULL, NULL, FALSE}};
2870 cm_return_val_if_fail(msginfo != NULL, -1);
2872 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2873 procheader_get_header_fields(fp, hentry);
2876 if (hentry[H_REPLY_TO].body != NULL) {
2877 if (hentry[H_REPLY_TO].body[0] != '\0') {
2879 conv_unmime_header(hentry[H_REPLY_TO].body,
2882 g_free(hentry[H_REPLY_TO].body);
2883 hentry[H_REPLY_TO].body = NULL;
2885 if (hentry[H_CC].body != NULL) {
2886 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2887 g_free(hentry[H_CC].body);
2888 hentry[H_CC].body = NULL;
2890 if (hentry[H_REFERENCES].body != NULL) {
2891 if (compose->mode == COMPOSE_REEDIT)
2892 compose->references = hentry[H_REFERENCES].body;
2894 compose->references = compose_parse_references
2895 (hentry[H_REFERENCES].body, msginfo->msgid);
2896 g_free(hentry[H_REFERENCES].body);
2898 hentry[H_REFERENCES].body = NULL;
2900 if (hentry[H_BCC].body != NULL) {
2901 if (compose->mode == COMPOSE_REEDIT)
2903 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2904 g_free(hentry[H_BCC].body);
2905 hentry[H_BCC].body = NULL;
2907 if (hentry[H_NEWSGROUPS].body != NULL) {
2908 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2909 hentry[H_NEWSGROUPS].body = NULL;
2911 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2912 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2913 compose->followup_to =
2914 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2917 g_free(hentry[H_FOLLOWUP_TO].body);
2918 hentry[H_FOLLOWUP_TO].body = NULL;
2920 if (hentry[H_LIST_POST].body != NULL) {
2921 gchar *to = NULL, *start = NULL;
2923 extract_address(hentry[H_LIST_POST].body);
2924 if (hentry[H_LIST_POST].body[0] != '\0') {
2925 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2927 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2928 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2931 g_free(compose->ml_post);
2932 compose->ml_post = to;
2935 g_free(hentry[H_LIST_POST].body);
2936 hentry[H_LIST_POST].body = NULL;
2939 /* CLAWS - X-Priority */
2940 if (compose->mode == COMPOSE_REEDIT)
2941 if (hentry[H_X_PRIORITY].body != NULL) {
2944 priority = atoi(hentry[H_X_PRIORITY].body);
2945 g_free(hentry[H_X_PRIORITY].body);
2947 hentry[H_X_PRIORITY].body = NULL;
2949 if (priority < PRIORITY_HIGHEST ||
2950 priority > PRIORITY_LOWEST)
2951 priority = PRIORITY_NORMAL;
2953 compose->priority = priority;
2956 if (compose->mode == COMPOSE_REEDIT) {
2957 if (msginfo->inreplyto && *msginfo->inreplyto)
2958 compose->inreplyto = g_strdup(msginfo->inreplyto);
2962 if (msginfo->msgid && *msginfo->msgid)
2963 compose->inreplyto = g_strdup(msginfo->msgid);
2965 if (!compose->references) {
2966 if (msginfo->msgid && *msginfo->msgid) {
2967 if (msginfo->inreplyto && *msginfo->inreplyto)
2968 compose->references =
2969 g_strdup_printf("<%s>\n\t<%s>",
2973 compose->references =
2974 g_strconcat("<", msginfo->msgid, ">",
2976 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2977 compose->references =
2978 g_strconcat("<", msginfo->inreplyto, ">",
2986 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2991 cm_return_val_if_fail(msginfo != NULL, -1);
2993 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2994 procheader_get_header_fields(fp, entries);
2998 while (he != NULL && he->name != NULL) {
3000 GtkListStore *model = NULL;
3002 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
3003 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
3004 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
3005 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
3006 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
3013 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
3015 GSList *ref_id_list, *cur;
3019 ref_id_list = references_list_append(NULL, ref);
3020 if (!ref_id_list) return NULL;
3021 if (msgid && *msgid)
3022 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
3027 for (cur = ref_id_list; cur != NULL; cur = cur->next)
3028 /* "<" + Message-ID + ">" + CR+LF+TAB */
3029 len += strlen((gchar *)cur->data) + 5;
3031 if (len > MAX_REFERENCES_LEN) {
3032 /* remove second message-ID */
3033 if (ref_id_list && ref_id_list->next &&
3034 ref_id_list->next->next) {
3035 g_free(ref_id_list->next->data);
3036 ref_id_list = g_slist_remove
3037 (ref_id_list, ref_id_list->next->data);
3039 slist_free_strings_full(ref_id_list);
3046 new_ref = g_string_new("");
3047 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
3048 if (new_ref->len > 0)
3049 g_string_append(new_ref, "\n\t");
3050 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3053 slist_free_strings_full(ref_id_list);
3055 new_ref_str = new_ref->str;
3056 g_string_free(new_ref, FALSE);
3061 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3062 const gchar *fmt, const gchar *qmark,
3063 const gchar *body, gboolean rewrap,
3064 gboolean need_unescape,
3065 const gchar *err_msg)
3067 MsgInfo* dummyinfo = NULL;
3068 gchar *quote_str = NULL;
3070 gboolean prev_autowrap;
3071 const gchar *trimmed_body = body;
3072 gint cursor_pos = -1;
3073 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3074 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3079 SIGNAL_BLOCK(buffer);
3082 dummyinfo = compose_msginfo_new_from_compose(compose);
3083 msginfo = dummyinfo;
3086 if (qmark != NULL) {
3088 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3089 compose->gtkaspell);
3091 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3093 quote_fmt_scan_string(qmark);
3096 buf = quote_fmt_get_buffer();
3098 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3100 Xstrdup_a(quote_str, buf, goto error)
3103 if (fmt && *fmt != '\0') {
3106 while (*trimmed_body == '\n')
3110 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3111 compose->gtkaspell);
3113 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3115 if (need_unescape) {
3118 /* decode \-escape sequences in the internal representation of the quote format */
3119 tmp = g_malloc(strlen(fmt)+1);
3120 pref_get_unescaped_pref(tmp, fmt);
3121 quote_fmt_scan_string(tmp);
3125 quote_fmt_scan_string(fmt);
3129 buf = quote_fmt_get_buffer();
3131 gint line = quote_fmt_get_line();
3132 alertpanel_error(err_msg, line);
3138 prev_autowrap = compose->autowrap;
3139 compose->autowrap = FALSE;
3141 mark = gtk_text_buffer_get_insert(buffer);
3142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3143 if (g_utf8_validate(buf, -1, NULL)) {
3144 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3146 gchar *tmpout = NULL;
3147 tmpout = conv_codeset_strdup
3148 (buf, conv_get_locale_charset_str_no_utf8(),
3150 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3152 tmpout = g_malloc(strlen(buf)*2+1);
3153 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3155 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3159 cursor_pos = quote_fmt_get_cursor_pos();
3160 if (cursor_pos == -1)
3161 cursor_pos = gtk_text_iter_get_offset(&iter);
3162 compose->set_cursor_pos = cursor_pos;
3164 gtk_text_buffer_get_start_iter(buffer, &iter);
3165 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3166 gtk_text_buffer_place_cursor(buffer, &iter);
3168 compose->autowrap = prev_autowrap;
3169 if (compose->autowrap && rewrap)
3170 compose_wrap_all(compose);
3177 SIGNAL_UNBLOCK(buffer);
3179 procmsg_msginfo_free( dummyinfo );
3184 /* if ml_post is of type addr@host and from is of type
3185 * addr-anything@host, return TRUE
3187 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3189 gchar *left_ml = NULL;
3190 gchar *right_ml = NULL;
3191 gchar *left_from = NULL;
3192 gchar *right_from = NULL;
3193 gboolean result = FALSE;
3195 if (!ml_post || !from)
3198 left_ml = g_strdup(ml_post);
3199 if (strstr(left_ml, "@")) {
3200 right_ml = strstr(left_ml, "@")+1;
3201 *(strstr(left_ml, "@")) = '\0';
3204 left_from = g_strdup(from);
3205 if (strstr(left_from, "@")) {
3206 right_from = strstr(left_from, "@")+1;
3207 *(strstr(left_from, "@")) = '\0';
3210 if (right_ml && right_from
3211 && !strncmp(left_from, left_ml, strlen(left_ml))
3212 && !strcmp(right_from, right_ml)) {
3221 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3222 gboolean respect_default_to)
3226 if (!folder || !folder->prefs)
3229 if (respect_default_to && folder->prefs->enable_default_to) {
3230 compose_entry_append(compose, folder->prefs->default_to,
3231 COMPOSE_TO, PREF_FOLDER);
3232 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3234 if (folder->prefs->enable_default_cc)
3235 compose_entry_append(compose, folder->prefs->default_cc,
3236 COMPOSE_CC, PREF_FOLDER);
3237 if (folder->prefs->enable_default_bcc)
3238 compose_entry_append(compose, folder->prefs->default_bcc,
3239 COMPOSE_BCC, PREF_FOLDER);
3240 if (folder->prefs->enable_default_replyto)
3241 compose_entry_append(compose, folder->prefs->default_replyto,
3242 COMPOSE_REPLYTO, PREF_FOLDER);
3245 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3250 if (!compose || !msginfo)
3253 if (msginfo->subject && *msginfo->subject) {
3254 buf = p = g_strdup(msginfo->subject);
3255 p += subject_get_prefix_length(p);
3256 memmove(buf, p, strlen(p) + 1);
3258 buf2 = g_strdup_printf("Re: %s", buf);
3259 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3264 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3267 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3268 gboolean to_all, gboolean to_ml,
3270 gboolean followup_and_reply_to)
3272 GSList *cc_list = NULL;
3275 gchar *replyto = NULL;
3276 gchar *ac_email = NULL;
3278 gboolean reply_to_ml = FALSE;
3279 gboolean default_reply_to = FALSE;
3281 cm_return_if_fail(compose->account != NULL);
3282 cm_return_if_fail(msginfo != NULL);
3284 reply_to_ml = to_ml && compose->ml_post;
3286 default_reply_to = msginfo->folder &&
3287 msginfo->folder->prefs->enable_default_reply_to;
3289 if (compose->account->protocol != A_NNTP) {
3290 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3292 if (reply_to_ml && !default_reply_to) {
3294 gboolean is_subscr = is_subscription(compose->ml_post,
3297 /* normal answer to ml post with a reply-to */
3298 compose_entry_append(compose,
3300 COMPOSE_TO, PREF_ML);
3301 if (compose->replyto)
3302 compose_entry_append(compose,
3304 COMPOSE_CC, PREF_ML);
3306 /* answer to subscription confirmation */
3307 if (compose->replyto)
3308 compose_entry_append(compose,
3310 COMPOSE_TO, PREF_ML);
3311 else if (msginfo->from)
3312 compose_entry_append(compose,
3314 COMPOSE_TO, PREF_ML);
3317 else if (!(to_all || to_sender) && default_reply_to) {
3318 compose_entry_append(compose,
3319 msginfo->folder->prefs->default_reply_to,
3320 COMPOSE_TO, PREF_FOLDER);
3321 compose_entry_mark_default_to(compose,
3322 msginfo->folder->prefs->default_reply_to);
3328 compose_entry_append(compose, msginfo->from,
3329 COMPOSE_TO, PREF_NONE);
3331 Xstrdup_a(tmp1, msginfo->from, return);
3332 extract_address(tmp1);
3333 compose_entry_append(compose,
3334 (!account_find_from_address(tmp1, FALSE))
3337 COMPOSE_TO, PREF_NONE);
3339 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3340 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3341 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3342 if (compose->replyto) {
3343 compose_entry_append(compose,
3345 COMPOSE_TO, PREF_NONE);
3347 compose_entry_append(compose,
3348 msginfo->from ? msginfo->from : "",
3349 COMPOSE_TO, PREF_NONE);
3352 /* replying to own mail, use original recp */
3353 compose_entry_append(compose,
3354 msginfo->to ? msginfo->to : "",
3355 COMPOSE_TO, PREF_NONE);
3356 compose_entry_append(compose,
3357 msginfo->cc ? msginfo->cc : "",
3358 COMPOSE_CC, PREF_NONE);
3363 if (to_sender || (compose->followup_to &&
3364 !strncmp(compose->followup_to, "poster", 6)))
3365 compose_entry_append
3367 (compose->replyto ? compose->replyto :
3368 msginfo->from ? msginfo->from : ""),
3369 COMPOSE_TO, PREF_NONE);
3371 else if (followup_and_reply_to || to_all) {
3372 compose_entry_append
3374 (compose->replyto ? compose->replyto :
3375 msginfo->from ? msginfo->from : ""),
3376 COMPOSE_TO, PREF_NONE);
3378 compose_entry_append
3380 compose->followup_to ? compose->followup_to :
3381 compose->newsgroups ? compose->newsgroups : "",
3382 COMPOSE_NEWSGROUPS, PREF_NONE);
3385 compose_entry_append
3387 compose->followup_to ? compose->followup_to :
3388 compose->newsgroups ? compose->newsgroups : "",
3389 COMPOSE_NEWSGROUPS, PREF_NONE);
3391 compose_reply_set_subject(compose, msginfo);
3393 if (to_ml && compose->ml_post) return;
3394 if (!to_all || compose->account->protocol == A_NNTP) return;
3396 if (compose->replyto) {
3397 Xstrdup_a(replyto, compose->replyto, return);
3398 extract_address(replyto);
3400 if (msginfo->from) {
3401 Xstrdup_a(from, msginfo->from, return);
3402 extract_address(from);
3405 if (replyto && from)
3406 cc_list = address_list_append_with_comments(cc_list, from);
3407 if (to_all && msginfo->folder &&
3408 msginfo->folder->prefs->enable_default_reply_to)
3409 cc_list = address_list_append_with_comments(cc_list,
3410 msginfo->folder->prefs->default_reply_to);
3411 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3412 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3414 ac_email = g_utf8_strdown(compose->account->address, -1);
3417 for (cur = cc_list; cur != NULL; cur = cur->next) {
3418 gchar *addr = g_utf8_strdown(cur->data, -1);
3419 extract_address(addr);
3421 if (strcmp(ac_email, addr))
3422 compose_entry_append(compose, (gchar *)cur->data,
3423 COMPOSE_CC, PREF_NONE);
3425 debug_print("Cc address same as compose account's, ignoring\n");
3430 slist_free_strings_full(cc_list);
3436 #define SET_ENTRY(entry, str) \
3439 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3442 #define SET_ADDRESS(type, str) \
3445 compose_entry_append(compose, str, type, PREF_NONE); \
3448 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3450 cm_return_if_fail(msginfo != NULL);
3452 SET_ENTRY(subject_entry, msginfo->subject);
3453 SET_ENTRY(from_name, msginfo->from);
3454 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3455 SET_ADDRESS(COMPOSE_CC, compose->cc);
3456 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3457 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3458 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3459 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3461 compose_update_priority_menu_item(compose);
3462 compose_update_privacy_system_menu_item(compose, FALSE);
3463 compose_show_first_last_header(compose, TRUE);
3469 static void compose_insert_sig(Compose *compose, gboolean replace)
3471 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3472 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3474 GtkTextIter iter, iter_end;
3475 gint cur_pos, ins_pos;
3476 gboolean prev_autowrap;
3477 gboolean found = FALSE;
3478 gboolean exists = FALSE;
3480 cm_return_if_fail(compose->account != NULL);
3484 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3485 G_CALLBACK(compose_changed_cb),
3488 mark = gtk_text_buffer_get_insert(buffer);
3489 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3490 cur_pos = gtk_text_iter_get_offset (&iter);
3493 gtk_text_buffer_get_end_iter(buffer, &iter);
3495 exists = (compose->sig_str != NULL);
3498 GtkTextIter first_iter, start_iter, end_iter;
3500 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3502 if (!exists || compose->sig_str[0] == '\0')
3505 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3506 compose->signature_tag);
3509 /* include previous \n\n */
3510 gtk_text_iter_backward_chars(&first_iter, 1);
3511 start_iter = first_iter;
3512 end_iter = first_iter;
3514 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3515 compose->signature_tag);
3516 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3517 compose->signature_tag);
3519 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3525 g_free(compose->sig_str);
3526 compose->sig_str = account_get_signature_str(compose->account);
3528 cur_pos = gtk_text_iter_get_offset(&iter);
3530 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3531 g_free(compose->sig_str);
3532 compose->sig_str = NULL;
3534 if (compose->sig_inserted == FALSE)
3535 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3536 compose->sig_inserted = TRUE;
3538 cur_pos = gtk_text_iter_get_offset(&iter);
3539 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3541 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3542 gtk_text_iter_forward_chars(&iter, 1);
3543 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3544 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3546 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3547 cur_pos = gtk_text_buffer_get_char_count (buffer);
3550 /* put the cursor where it should be
3551 * either where the quote_fmt says, either where it was */
3552 if (compose->set_cursor_pos < 0)
3553 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3555 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3556 compose->set_cursor_pos);
3558 compose->set_cursor_pos = -1;
3559 gtk_text_buffer_place_cursor(buffer, &iter);
3560 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(compose_changed_cb),
3567 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3570 GtkTextBuffer *buffer;
3573 const gchar *cur_encoding;
3574 gchar buf[BUFFSIZE];
3577 gboolean prev_autowrap;
3578 struct stat file_stat;
3580 GString *file_contents = NULL;
3582 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3584 /* get the size of the file we are about to insert */
3585 ret = g_stat(file, &file_stat);
3587 gchar *shortfile = g_path_get_basename(file);
3588 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3590 return COMPOSE_INSERT_NO_FILE;
3591 } else if (prefs_common.warn_large_insert == TRUE) {
3593 /* ask user for confirmation if the file is large */
3594 if (prefs_common.warn_large_insert_size < 0 ||
3595 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3599 msg = g_strdup_printf(_("You are about to insert a file of %s "
3600 "in the message body. Are you sure you want to do that?"),
3601 to_human_readable(file_stat.st_size));
3602 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3603 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3606 /* do we ask for confirmation next time? */
3607 if (aval & G_ALERTDISABLE) {
3608 /* no confirmation next time, disable feature in preferences */
3609 aval &= ~G_ALERTDISABLE;
3610 prefs_common.warn_large_insert = FALSE;
3613 /* abort file insertion if user canceled action */
3614 if (aval != G_ALERTALTERNATE) {
3615 return COMPOSE_INSERT_NO_FILE;
3621 if ((fp = g_fopen(file, "rb")) == NULL) {
3622 FILE_OP_ERROR(file, "fopen");
3623 return COMPOSE_INSERT_READ_ERROR;
3626 prev_autowrap = compose->autowrap;
3627 compose->autowrap = FALSE;
3629 text = GTK_TEXT_VIEW(compose->text);
3630 buffer = gtk_text_view_get_buffer(text);
3631 mark = gtk_text_buffer_get_insert(buffer);
3632 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3634 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3635 G_CALLBACK(text_inserted),
3638 cur_encoding = conv_get_locale_charset_str_no_utf8();
3640 file_contents = g_string_new("");
3641 while (fgets(buf, sizeof(buf), fp) != NULL) {
3644 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3645 str = g_strdup(buf);
3647 str = conv_codeset_strdup
3648 (buf, cur_encoding, CS_INTERNAL);
3651 /* strip <CR> if DOS/Windows file,
3652 replace <CR> with <LF> if Macintosh file. */
3655 if (len > 0 && str[len - 1] != '\n') {
3657 if (str[len] == '\r') str[len] = '\n';
3660 file_contents = g_string_append(file_contents, str);
3664 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3665 g_string_free(file_contents, TRUE);
3667 compose_changed_cb(NULL, compose);
3668 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3669 G_CALLBACK(text_inserted),
3671 compose->autowrap = prev_autowrap;
3672 if (compose->autowrap)
3673 compose_wrap_all(compose);
3677 return COMPOSE_INSERT_SUCCESS;
3680 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3681 const gchar *filename,
3682 const gchar *content_type,
3683 const gchar *charset)
3691 GtkListStore *store;
3693 gboolean has_binary = FALSE;
3695 if (!is_file_exist(file)) {
3696 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3697 gboolean result = FALSE;
3698 if (file_from_uri && is_file_exist(file_from_uri)) {
3699 result = compose_attach_append(
3700 compose, file_from_uri,
3701 filename, content_type,
3704 g_free(file_from_uri);
3707 alertpanel_error("File %s doesn't exist\n", filename);
3710 if ((size = get_file_size(file)) < 0) {
3711 alertpanel_error("Can't get file size of %s\n", filename);
3715 /* In batch mode, we allow 0-length files to be attached no questions asked */
3716 if (size == 0 && !compose->batch) {
3717 gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
3718 AlertValue aval = alertpanel_full(_("Empty file"), msg,
3719 GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
3720 NULL, ALERT_WARNING, G_ALERTDEFAULT);
3723 if (aval != G_ALERTALTERNATE) {
3727 if ((fp = g_fopen(file, "rb")) == NULL) {
3728 alertpanel_error(_("Can't read %s."), filename);
3733 ainfo = g_new0(AttachInfo, 1);
3734 auto_ainfo = g_auto_pointer_new_with_free
3735 (ainfo, (GFreeFunc) compose_attach_info_free);
3736 ainfo->file = g_strdup(file);
3739 ainfo->content_type = g_strdup(content_type);
3740 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3742 MsgFlags flags = {0, 0};
3744 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3745 ainfo->encoding = ENC_7BIT;
3747 ainfo->encoding = ENC_8BIT;
3749 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3750 if (msginfo && msginfo->subject)
3751 name = g_strdup(msginfo->subject);
3753 name = g_path_get_basename(filename ? filename : file);
3755 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3757 procmsg_msginfo_free(msginfo);
3759 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3760 ainfo->charset = g_strdup(charset);
3761 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3763 ainfo->encoding = ENC_BASE64;
3765 name = g_path_get_basename(filename ? filename : file);
3766 ainfo->name = g_strdup(name);
3770 ainfo->content_type = procmime_get_mime_type(file);
3771 if (!ainfo->content_type) {
3772 ainfo->content_type =
3773 g_strdup("application/octet-stream");
3774 ainfo->encoding = ENC_BASE64;
3775 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3777 procmime_get_encoding_for_text_file(file, &has_binary);
3779 ainfo->encoding = ENC_BASE64;
3780 name = g_path_get_basename(filename ? filename : file);
3781 ainfo->name = g_strdup(name);
3785 if (ainfo->name != NULL
3786 && !strcmp(ainfo->name, ".")) {
3787 g_free(ainfo->name);
3791 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3792 g_free(ainfo->content_type);
3793 ainfo->content_type = g_strdup("application/octet-stream");
3794 g_free(ainfo->charset);
3795 ainfo->charset = NULL;
3798 ainfo->size = (goffset)size;
3799 size_text = to_human_readable((goffset)size);
3801 store = GTK_LIST_STORE(gtk_tree_view_get_model
3802 (GTK_TREE_VIEW(compose->attach_clist)));
3804 gtk_list_store_append(store, &iter);
3805 gtk_list_store_set(store, &iter,
3806 COL_MIMETYPE, ainfo->content_type,
3807 COL_SIZE, size_text,
3808 COL_NAME, ainfo->name,
3809 COL_CHARSET, ainfo->charset,
3811 COL_AUTODATA, auto_ainfo,
3814 g_auto_pointer_free(auto_ainfo);
3815 compose_attach_update_label(compose);
3819 static void compose_use_signing(Compose *compose, gboolean use_signing)
3821 compose->use_signing = use_signing;
3822 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3825 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3827 compose->use_encryption = use_encryption;
3828 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3831 #define NEXT_PART_NOT_CHILD(info) \
3833 node = info->node; \
3834 while (node->children) \
3835 node = g_node_last_child(node); \
3836 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3839 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3843 MimeInfo *firsttext = NULL;
3844 MimeInfo *encrypted = NULL;
3847 const gchar *partname = NULL;
3849 mimeinfo = procmime_scan_message(msginfo);
3850 if (!mimeinfo) return;
3852 if (mimeinfo->node->children == NULL) {
3853 procmime_mimeinfo_free_all(mimeinfo);
3857 /* find first content part */
3858 child = (MimeInfo *) mimeinfo->node->children->data;
3859 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3860 child = (MimeInfo *)child->node->children->data;
3863 if (child->type == MIMETYPE_TEXT) {
3865 debug_print("First text part found\n");
3866 } else if (compose->mode == COMPOSE_REEDIT &&
3867 child->type == MIMETYPE_APPLICATION &&
3868 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3869 encrypted = (MimeInfo *)child->node->parent->data;
3872 child = (MimeInfo *) mimeinfo->node->children->data;
3873 while (child != NULL) {
3876 if (child == encrypted) {
3877 /* skip this part of tree */
3878 NEXT_PART_NOT_CHILD(child);
3882 if (child->type == MIMETYPE_MULTIPART) {
3883 /* get the actual content */
3884 child = procmime_mimeinfo_next(child);
3888 if (child == firsttext) {
3889 child = procmime_mimeinfo_next(child);
3893 outfile = procmime_get_tmp_file_name(child);
3894 if ((err = procmime_get_part(outfile, child)) < 0)
3895 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3897 gchar *content_type;
3899 content_type = procmime_get_content_type_str(child->type, child->subtype);
3901 /* if we meet a pgp signature, we don't attach it, but
3902 * we force signing. */
3903 if ((strcmp(content_type, "application/pgp-signature") &&
3904 strcmp(content_type, "application/pkcs7-signature") &&
3905 strcmp(content_type, "application/x-pkcs7-signature"))
3906 || compose->mode == COMPOSE_REDIRECT) {
3907 partname = procmime_mimeinfo_get_parameter(child, "filename");
3908 if (partname == NULL)
3909 partname = procmime_mimeinfo_get_parameter(child, "name");
3910 if (partname == NULL)
3912 compose_attach_append(compose, outfile,
3913 partname, content_type,
3914 procmime_mimeinfo_get_parameter(child, "charset"));
3916 compose_force_signing(compose, compose->account, NULL);
3918 g_free(content_type);
3921 NEXT_PART_NOT_CHILD(child);
3923 procmime_mimeinfo_free_all(mimeinfo);
3926 #undef NEXT_PART_NOT_CHILD
3931 WAIT_FOR_INDENT_CHAR,
3932 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3935 /* return indent length, we allow:
3936 indent characters followed by indent characters or spaces/tabs,
3937 alphabets and numbers immediately followed by indent characters,
3938 and the repeating sequences of the above
3939 If quote ends with multiple spaces, only the first one is included. */
3940 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3941 const GtkTextIter *start, gint *len)
3943 GtkTextIter iter = *start;
3947 IndentState state = WAIT_FOR_INDENT_CHAR;
3950 gint alnum_count = 0;
3951 gint space_count = 0;
3954 if (prefs_common.quote_chars == NULL) {
3958 while (!gtk_text_iter_ends_line(&iter)) {
3959 wc = gtk_text_iter_get_char(&iter);
3960 if (g_unichar_iswide(wc))
3962 clen = g_unichar_to_utf8(wc, ch);
3966 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3967 is_space = g_unichar_isspace(wc);
3969 if (state == WAIT_FOR_INDENT_CHAR) {
3970 if (!is_indent && !g_unichar_isalnum(wc))
3973 quote_len += alnum_count + space_count + 1;
3974 alnum_count = space_count = 0;
3975 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3978 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3979 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3983 else if (is_indent) {
3984 quote_len += alnum_count + space_count + 1;
3985 alnum_count = space_count = 0;
3988 state = WAIT_FOR_INDENT_CHAR;
3992 gtk_text_iter_forward_char(&iter);
3995 if (quote_len > 0 && space_count > 0)
4001 if (quote_len > 0) {
4003 gtk_text_iter_forward_chars(&iter, quote_len);
4004 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
4010 /* return >0 if the line is itemized */
4011 static int compose_itemized_length(GtkTextBuffer *buffer,
4012 const GtkTextIter *start)
4014 GtkTextIter iter = *start;
4019 if (gtk_text_iter_ends_line(&iter))
4024 wc = gtk_text_iter_get_char(&iter);
4025 if (!g_unichar_isspace(wc))
4027 gtk_text_iter_forward_char(&iter);
4028 if (gtk_text_iter_ends_line(&iter))
4032 clen = g_unichar_to_utf8(wc, ch);
4036 if (!strchr("*-+", ch[0]))
4039 gtk_text_iter_forward_char(&iter);
4040 if (gtk_text_iter_ends_line(&iter))
4042 wc = gtk_text_iter_get_char(&iter);
4043 if (g_unichar_isspace(wc)) {
4049 /* return the string at the start of the itemization */
4050 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
4051 const GtkTextIter *start)
4053 GtkTextIter iter = *start;
4056 GString *item_chars = g_string_new("");
4059 if (gtk_text_iter_ends_line(&iter))
4064 wc = gtk_text_iter_get_char(&iter);
4065 if (!g_unichar_isspace(wc))
4067 gtk_text_iter_forward_char(&iter);
4068 if (gtk_text_iter_ends_line(&iter))
4070 g_string_append_unichar(item_chars, wc);
4073 str = item_chars->str;
4074 g_string_free(item_chars, FALSE);
4078 /* return the number of spaces at a line's start */
4079 static int compose_left_offset_length(GtkTextBuffer *buffer,
4080 const GtkTextIter *start)
4082 GtkTextIter iter = *start;
4085 if (gtk_text_iter_ends_line(&iter))
4089 wc = gtk_text_iter_get_char(&iter);
4090 if (!g_unichar_isspace(wc))
4093 gtk_text_iter_forward_char(&iter);
4094 if (gtk_text_iter_ends_line(&iter))
4098 gtk_text_iter_forward_char(&iter);
4099 if (gtk_text_iter_ends_line(&iter))
4104 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4105 const GtkTextIter *start,
4106 GtkTextIter *break_pos,
4110 GtkTextIter iter = *start, line_end = *start;
4111 PangoLogAttr *attrs;
4118 gboolean can_break = FALSE;
4119 gboolean do_break = FALSE;
4120 gboolean was_white = FALSE;
4121 gboolean prev_dont_break = FALSE;
4123 gtk_text_iter_forward_to_line_end(&line_end);
4124 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4125 len = g_utf8_strlen(str, -1);
4129 g_warning("compose_get_line_break_pos: len = 0!\n");
4133 /* g_print("breaking line: %d: %s (len = %d)\n",
4134 gtk_text_iter_get_line(&iter), str, len); */
4136 attrs = g_new(PangoLogAttr, len + 1);
4138 pango_default_break(str, -1, NULL, attrs, len + 1);
4142 /* skip quote and leading spaces */
4143 for (i = 0; *p != '\0' && i < len; i++) {
4146 wc = g_utf8_get_char(p);
4147 if (i >= quote_len && !g_unichar_isspace(wc))
4149 if (g_unichar_iswide(wc))
4151 else if (*p == '\t')
4155 p = g_utf8_next_char(p);
4158 for (; *p != '\0' && i < len; i++) {
4159 PangoLogAttr *attr = attrs + i;
4163 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4166 was_white = attr->is_white;
4168 /* don't wrap URI */
4169 if ((uri_len = get_uri_len(p)) > 0) {
4171 if (pos > 0 && col > max_col) {
4181 wc = g_utf8_get_char(p);
4182 if (g_unichar_iswide(wc)) {
4184 if (prev_dont_break && can_break && attr->is_line_break)
4186 } else if (*p == '\t')
4190 if (pos > 0 && col > max_col) {
4195 if (*p == '-' || *p == '/')
4196 prev_dont_break = TRUE;
4198 prev_dont_break = FALSE;
4200 p = g_utf8_next_char(p);
4204 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4209 *break_pos = *start;
4210 gtk_text_iter_set_line_offset(break_pos, pos);
4215 static gboolean compose_join_next_line(Compose *compose,
4216 GtkTextBuffer *buffer,
4218 const gchar *quote_str)
4220 GtkTextIter iter_ = *iter, cur, prev, next, end;
4221 PangoLogAttr attrs[3];
4223 gchar *next_quote_str;
4226 gboolean keep_cursor = FALSE;
4228 if (!gtk_text_iter_forward_line(&iter_) ||
4229 gtk_text_iter_ends_line(&iter_)) {
4232 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4234 if ((quote_str || next_quote_str) &&
4235 strcmp2(quote_str, next_quote_str) != 0) {
4236 g_free(next_quote_str);
4239 g_free(next_quote_str);
4242 if (quote_len > 0) {
4243 gtk_text_iter_forward_chars(&end, quote_len);
4244 if (gtk_text_iter_ends_line(&end)) {
4249 /* don't join itemized lines */
4250 if (compose_itemized_length(buffer, &end) > 0) {
4254 /* don't join signature separator */
4255 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4258 /* delete quote str */
4260 gtk_text_buffer_delete(buffer, &iter_, &end);
4262 /* don't join line breaks put by the user */
4264 gtk_text_iter_backward_char(&cur);
4265 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4266 gtk_text_iter_forward_char(&cur);
4270 gtk_text_iter_forward_char(&cur);
4271 /* delete linebreak and extra spaces */
4272 while (gtk_text_iter_backward_char(&cur)) {
4273 wc1 = gtk_text_iter_get_char(&cur);
4274 if (!g_unichar_isspace(wc1))
4279 while (!gtk_text_iter_ends_line(&cur)) {
4280 wc1 = gtk_text_iter_get_char(&cur);
4281 if (!g_unichar_isspace(wc1))
4283 gtk_text_iter_forward_char(&cur);
4286 if (!gtk_text_iter_equal(&prev, &next)) {
4289 mark = gtk_text_buffer_get_insert(buffer);
4290 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4291 if (gtk_text_iter_equal(&prev, &cur))
4293 gtk_text_buffer_delete(buffer, &prev, &next);
4297 /* insert space if required */
4298 gtk_text_iter_backward_char(&prev);
4299 wc1 = gtk_text_iter_get_char(&prev);
4300 wc2 = gtk_text_iter_get_char(&next);
4301 gtk_text_iter_forward_char(&next);
4302 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4303 pango_default_break(str, -1, NULL, attrs, 3);
4304 if (!attrs[1].is_line_break ||
4305 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4306 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4308 gtk_text_iter_backward_char(&iter_);
4309 gtk_text_buffer_place_cursor(buffer, &iter_);
4318 #define ADD_TXT_POS(bp_, ep_, pti_) \
4319 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4320 last = last->next; \
4321 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4322 last->next = NULL; \
4324 g_warning("alloc error scanning URIs\n"); \
4327 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4329 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4330 GtkTextBuffer *buffer;
4331 GtkTextIter iter, break_pos, end_of_line;
4332 gchar *quote_str = NULL;
4334 gboolean wrap_quote = force || prefs_common.linewrap_quote;
4335 gboolean prev_autowrap = compose->autowrap;
4336 gint startq_offset = -1, noq_offset = -1;
4337 gint uri_start = -1, uri_stop = -1;
4338 gint nouri_start = -1, nouri_stop = -1;
4339 gint num_blocks = 0;
4340 gint quotelevel = -1;
4341 gboolean modified = force;
4342 gboolean removed = FALSE;
4343 gboolean modified_before_remove = FALSE;
4345 gboolean start = TRUE;
4346 gint itemized_len = 0, rem_item_len = 0;
4347 gchar *itemized_chars = NULL;
4348 gboolean item_continuation = FALSE;
4353 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4357 compose->autowrap = FALSE;
4359 buffer = gtk_text_view_get_buffer(text);
4360 undo_wrapping(compose->undostruct, TRUE);
4365 mark = gtk_text_buffer_get_insert(buffer);
4366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4370 if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
4371 if (gtk_text_iter_ends_line(&iter)) {
4372 while (gtk_text_iter_ends_line(&iter) &&
4373 gtk_text_iter_forward_line(&iter))
4376 while (gtk_text_iter_backward_line(&iter)) {
4377 if (gtk_text_iter_ends_line(&iter)) {
4378 gtk_text_iter_forward_line(&iter);
4384 /* move to line start */
4385 gtk_text_iter_set_line_offset(&iter, 0);
4388 itemized_len = compose_itemized_length(buffer, &iter);
4390 if (!itemized_len) {
4391 itemized_len = compose_left_offset_length(buffer, &iter);
4392 item_continuation = TRUE;
4396 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4398 /* go until paragraph end (empty line) */
4399 while (start || !gtk_text_iter_ends_line(&iter)) {
4400 gchar *scanpos = NULL;
4401 /* parse table - in order of priority */
4403 const gchar *needle; /* token */
4405 /* token search function */
4406 gchar *(*search) (const gchar *haystack,
4407 const gchar *needle);
4408 /* part parsing function */
4409 gboolean (*parse) (const gchar *start,
4410 const gchar *scanpos,
4414 /* part to URI function */
4415 gchar *(*build_uri) (const gchar *bp,
4419 static struct table parser[] = {
4420 {"http://", strcasestr, get_uri_part, make_uri_string},
4421 {"https://", strcasestr, get_uri_part, make_uri_string},
4422 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4423 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4424 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4425 {"www.", strcasestr, get_uri_part, make_http_string},
4426 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4427 {"@", strcasestr, get_email_part, make_email_string}
4429 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4430 gint last_index = PARSE_ELEMS;
4432 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4436 if (!prev_autowrap && num_blocks == 0) {
4438 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4439 G_CALLBACK(text_inserted),
4442 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4445 uri_start = uri_stop = -1;
4447 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4450 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4451 if (startq_offset == -1)
4452 startq_offset = gtk_text_iter_get_offset(&iter);
4453 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4454 if (quotelevel > 2) {
4455 /* recycle colors */
4456 if (prefs_common.recycle_quote_colors)
4465 if (startq_offset == -1)
4466 noq_offset = gtk_text_iter_get_offset(&iter);
4470 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4473 if (gtk_text_iter_ends_line(&iter)) {
4475 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4476 prefs_common.linewrap_len,
4478 GtkTextIter prev, next, cur;
4479 if (prev_autowrap != FALSE || force) {
4480 compose->automatic_break = TRUE;
4482 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4483 compose->automatic_break = FALSE;
4484 if (itemized_len && compose->autoindent) {
4485 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4486 if (!item_continuation)
4487 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4489 } else if (quote_str && wrap_quote) {
4490 compose->automatic_break = TRUE;
4492 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4493 compose->automatic_break = FALSE;
4494 if (itemized_len && compose->autoindent) {
4495 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4496 if (!item_continuation)
4497 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4501 /* remove trailing spaces */
4503 rem_item_len = itemized_len;
4504 while (compose->autoindent && rem_item_len-- > 0)
4505 gtk_text_iter_backward_char(&cur);
4506 gtk_text_iter_backward_char(&cur);
4509 while (!gtk_text_iter_starts_line(&cur)) {
4512 gtk_text_iter_backward_char(&cur);
4513 wc = gtk_text_iter_get_char(&cur);
4514 if (!g_unichar_isspace(wc))
4518 if (!gtk_text_iter_equal(&prev, &next)) {
4519 gtk_text_buffer_delete(buffer, &prev, &next);
4521 gtk_text_iter_forward_char(&break_pos);
4525 gtk_text_buffer_insert(buffer, &break_pos,
4529 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4531 /* move iter to current line start */
4532 gtk_text_iter_set_line_offset(&iter, 0);
4539 /* move iter to next line start */
4545 if (!prev_autowrap && num_blocks > 0) {
4547 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4548 G_CALLBACK(text_inserted),
4552 while (!gtk_text_iter_ends_line(&end_of_line)) {
4553 gtk_text_iter_forward_char(&end_of_line);
4555 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4557 nouri_start = gtk_text_iter_get_offset(&iter);
4558 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4560 walk_pos = gtk_text_iter_get_offset(&iter);
4561 /* FIXME: this looks phony. scanning for anything in the parse table */
4562 for (n = 0; n < PARSE_ELEMS; n++) {
4565 tmp = parser[n].search(walk, parser[n].needle);
4567 if (scanpos == NULL || tmp < scanpos) {
4576 /* check if URI can be parsed */
4577 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4578 (const gchar **)&ep, FALSE)
4579 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4583 strlen(parser[last_index].needle);
4586 uri_start = walk_pos + (bp - o_walk);
4587 uri_stop = walk_pos + (ep - o_walk);
4591 gtk_text_iter_forward_line(&iter);
4594 if (startq_offset != -1) {
4595 GtkTextIter startquote, endquote;
4596 gtk_text_buffer_get_iter_at_offset(
4597 buffer, &startquote, startq_offset);
4600 switch (quotelevel) {
4602 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4603 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4604 gtk_text_buffer_apply_tag_by_name(
4605 buffer, "quote0", &startquote, &endquote);
4606 gtk_text_buffer_remove_tag_by_name(
4607 buffer, "quote1", &startquote, &endquote);
4608 gtk_text_buffer_remove_tag_by_name(
4609 buffer, "quote2", &startquote, &endquote);
4614 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4615 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4616 gtk_text_buffer_apply_tag_by_name(
4617 buffer, "quote1", &startquote, &endquote);
4618 gtk_text_buffer_remove_tag_by_name(
4619 buffer, "quote0", &startquote, &endquote);
4620 gtk_text_buffer_remove_tag_by_name(
4621 buffer, "quote2", &startquote, &endquote);
4626 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4627 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4628 gtk_text_buffer_apply_tag_by_name(
4629 buffer, "quote2", &startquote, &endquote);
4630 gtk_text_buffer_remove_tag_by_name(
4631 buffer, "quote0", &startquote, &endquote);
4632 gtk_text_buffer_remove_tag_by_name(
4633 buffer, "quote1", &startquote, &endquote);
4639 } else if (noq_offset != -1) {
4640 GtkTextIter startnoquote, endnoquote;
4641 gtk_text_buffer_get_iter_at_offset(
4642 buffer, &startnoquote, noq_offset);
4645 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4646 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4647 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4648 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4649 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4650 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4651 gtk_text_buffer_remove_tag_by_name(
4652 buffer, "quote0", &startnoquote, &endnoquote);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer, "quote1", &startnoquote, &endnoquote);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer, "quote2", &startnoquote, &endnoquote);
4662 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4663 GtkTextIter nouri_start_iter, nouri_end_iter;
4664 gtk_text_buffer_get_iter_at_offset(
4665 buffer, &nouri_start_iter, nouri_start);
4666 gtk_text_buffer_get_iter_at_offset(
4667 buffer, &nouri_end_iter, nouri_stop);
4668 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4669 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4670 gtk_text_buffer_remove_tag_by_name(
4671 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4672 modified_before_remove = modified;
4677 if (uri_start >= 0 && uri_stop > 0) {
4678 GtkTextIter uri_start_iter, uri_end_iter, back;
4679 gtk_text_buffer_get_iter_at_offset(
4680 buffer, &uri_start_iter, uri_start);
4681 gtk_text_buffer_get_iter_at_offset(
4682 buffer, &uri_end_iter, uri_stop);
4683 back = uri_end_iter;
4684 gtk_text_iter_backward_char(&back);
4685 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4686 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4687 gtk_text_buffer_apply_tag_by_name(
4688 buffer, "link", &uri_start_iter, &uri_end_iter);
4690 if (removed && !modified_before_remove) {
4696 // debug_print("not modified, out after %d lines\n", lines);
4700 // debug_print("modified, out after %d lines\n", lines);
4702 g_free(itemized_chars);
4705 undo_wrapping(compose->undostruct, FALSE);
4706 compose->autowrap = prev_autowrap;
4711 void compose_action_cb(void *data)
4713 Compose *compose = (Compose *)data;
4714 compose_wrap_all(compose);
4717 static void compose_wrap_all(Compose *compose)
4719 compose_wrap_all_full(compose, FALSE);
4722 static void compose_wrap_all_full(Compose *compose, gboolean force)
4724 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4725 GtkTextBuffer *buffer;
4727 gboolean modified = TRUE;
4729 buffer = gtk_text_view_get_buffer(text);
4731 gtk_text_buffer_get_start_iter(buffer, &iter);
4733 undo_wrapping(compose->undostruct, TRUE);
4735 while (!gtk_text_iter_is_end(&iter) && modified)
4736 modified = compose_beautify_paragraph(compose, &iter, force);
4738 undo_wrapping(compose->undostruct, FALSE);
4742 static void compose_set_title(Compose *compose)
4748 edited = compose->modified ? _(" [Edited]") : "";
4750 subject = gtk_editable_get_chars(
4751 GTK_EDITABLE(compose->subject_entry), 0, -1);
4753 #ifndef GENERIC_UMPC
4754 if (subject && strlen(subject))
4755 str = g_strdup_printf(_("%s - Compose message%s"),
4758 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4760 str = g_strdup(_("Compose message"));
4763 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4769 * compose_current_mail_account:
4771 * Find a current mail account (the currently selected account, or the
4772 * default account, if a news account is currently selected). If a
4773 * mail account cannot be found, display an error message.
4775 * Return value: Mail account, or NULL if not found.
4777 static PrefsAccount *
4778 compose_current_mail_account(void)
4782 if (cur_account && cur_account->protocol != A_NNTP)
4785 ac = account_get_default();
4786 if (!ac || ac->protocol == A_NNTP) {
4787 alertpanel_error(_("Account for sending mail is not specified.\n"
4788 "Please select a mail account before sending."));
4795 #define QUOTE_IF_REQUIRED(out, str) \
4797 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4801 len = strlen(str) + 3; \
4802 if ((__tmp = alloca(len)) == NULL) { \
4803 g_warning("can't allocate memory\n"); \
4804 g_string_free(header, TRUE); \
4807 g_snprintf(__tmp, len, "\"%s\"", str); \
4812 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4813 g_warning("can't allocate memory\n"); \
4814 g_string_free(header, TRUE); \
4817 strcpy(__tmp, str); \
4823 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4825 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4829 len = strlen(str) + 3; \
4830 if ((__tmp = alloca(len)) == NULL) { \
4831 g_warning("can't allocate memory\n"); \
4834 g_snprintf(__tmp, len, "\"%s\"", str); \
4839 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4840 g_warning("can't allocate memory\n"); \
4843 strcpy(__tmp, str); \
4849 static void compose_select_account(Compose *compose, PrefsAccount *account,
4852 gchar *from = NULL, *header = NULL;
4853 ComposeHeaderEntry *header_entry;
4854 #if GTK_CHECK_VERSION(2, 24, 0)
4858 cm_return_if_fail(account != NULL);
4860 compose->account = account;
4861 if (account->name && *account->name) {
4863 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4864 qbuf = escape_internal_quotes(buf, '"');
4865 from = g_strdup_printf("%s <%s>",
4866 qbuf, account->address);
4869 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4871 from = g_strdup_printf("<%s>",
4873 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4878 compose_set_title(compose);
4880 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4881 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4883 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4884 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4887 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4889 activate_privacy_system(compose, account, FALSE);
4891 if (!init && compose->mode != COMPOSE_REDIRECT) {
4892 undo_block(compose->undostruct);
4893 compose_insert_sig(compose, TRUE);
4894 undo_unblock(compose->undostruct);
4897 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4898 #if !GTK_CHECK_VERSION(2, 24, 0)
4899 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4901 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4902 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4903 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4906 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4907 if (account->protocol == A_NNTP) {
4908 if (!strcmp(header, _("To:")))
4909 combobox_select_by_text(
4910 GTK_COMBO_BOX(header_entry->combo),
4913 if (!strcmp(header, _("Newsgroups:")))
4914 combobox_select_by_text(
4915 GTK_COMBO_BOX(header_entry->combo),
4923 /* use account's dict info if set */
4924 if (compose->gtkaspell) {
4925 if (account->enable_default_dictionary)
4926 gtkaspell_change_dict(compose->gtkaspell,
4927 account->default_dictionary, FALSE);
4928 if (account->enable_default_alt_dictionary)
4929 gtkaspell_change_alt_dict(compose->gtkaspell,
4930 account->default_alt_dictionary);
4931 if (account->enable_default_dictionary
4932 || account->enable_default_alt_dictionary)
4933 compose_spell_menu_changed(compose);
4938 gboolean compose_check_for_valid_recipient(Compose *compose) {
4939 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4940 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4941 gboolean recipient_found = FALSE;
4945 /* free to and newsgroup list */
4946 slist_free_strings_full(compose->to_list);
4947 compose->to_list = NULL;
4949 slist_free_strings_full(compose->newsgroup_list);
4950 compose->newsgroup_list = NULL;
4952 /* search header entries for to and newsgroup entries */
4953 for (list = compose->header_list; list; list = list->next) {
4956 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4957 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4960 if (entry[0] != '\0') {
4961 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4962 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4963 compose->to_list = address_list_append(compose->to_list, entry);
4964 recipient_found = TRUE;
4967 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4968 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4969 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4970 recipient_found = TRUE;
4977 return recipient_found;
4980 static gboolean compose_check_for_set_recipients(Compose *compose)
4982 if (compose->account->set_autocc && compose->account->auto_cc) {
4983 gboolean found_other = FALSE;
4985 /* search header entries for to and newsgroup entries */
4986 for (list = compose->header_list; list; list = list->next) {
4989 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4990 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4993 if (strcmp(entry, compose->account->auto_cc)
4994 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
5004 if (compose->batch) {
5005 gtk_widget_show_all(compose->window);
5007 aval = alertpanel(_("Send"),
5008 _("The only recipient is the default CC address. Send anyway?"),
5009 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5010 if (aval != G_ALERTALTERNATE)
5014 if (compose->account->set_autobcc && compose->account->auto_bcc) {
5015 gboolean found_other = FALSE;
5017 /* search header entries for to and newsgroup entries */
5018 for (list = compose->header_list; list; list = list->next) {
5021 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
5022 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
5025 if (strcmp(entry, compose->account->auto_bcc)
5026 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
5036 if (compose->batch) {
5037 gtk_widget_show_all(compose->window);
5039 aval = alertpanel(_("Send"),
5040 _("The only recipient is the default BCC address. Send anyway?"),
5041 GTK_STOCK_CANCEL, _("+_Send"), NULL);
5042 if (aval != G_ALERTALTERNATE)
5049 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
5053 if (compose_check_for_valid_recipient(compose) == FALSE) {
5054 if (compose->batch) {
5055 gtk_widget_show_all(compose->window);
5057 alertpanel_error(_("Recipient is not specified."));
5061 if (compose_check_for_set_recipients(compose) == FALSE) {
5065 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5066 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5067 if (*str == '\0' && check_everything == TRUE &&
5068 compose->mode != COMPOSE_REDIRECT) {
5070 gchar *button_label;
5073 if (compose->sending)
5074 button_label = _("+_Send");
5076 button_label = _("+_Queue");
5077 message = g_strdup_printf(_("Subject is empty. %s"),
5078 compose->sending?_("Send it anyway?"):
5079 _("Queue it anyway?"));
5081 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5082 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5083 ALERT_QUESTION, G_ALERTDEFAULT);
5085 if (aval & G_ALERTDISABLE) {
5086 aval &= ~G_ALERTDISABLE;
5087 prefs_common.warn_empty_subj = FALSE;
5089 if (aval != G_ALERTALTERNATE)
5094 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5100 gint compose_send(Compose *compose)
5103 FolderItem *folder = NULL;
5105 gchar *msgpath = NULL;
5106 gboolean discard_window = FALSE;
5107 gchar *errstr = NULL;
5108 gchar *tmsgid = NULL;
5109 MainWindow *mainwin = mainwindow_get_mainwindow();
5110 gboolean queued_removed = FALSE;
5112 if (prefs_common.send_dialog_invisible
5113 || compose->batch == TRUE)
5114 discard_window = TRUE;
5116 compose_allow_user_actions (compose, FALSE);
5117 compose->sending = TRUE;
5119 if (compose_check_entries(compose, TRUE) == FALSE) {
5120 if (compose->batch) {
5121 gtk_widget_show_all(compose->window);
5127 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5130 if (compose->batch) {
5131 gtk_widget_show_all(compose->window);
5134 alertpanel_error(_("Could not queue message for sending:\n\n"
5135 "Charset conversion failed."));
5136 } else if (val == -5) {
5137 alertpanel_error(_("Could not queue message for sending:\n\n"
5138 "Couldn't get recipient encryption key."));
5139 } else if (val == -6) {
5141 } else if (val == -3) {
5142 if (privacy_peek_error())
5143 alertpanel_error(_("Could not queue message for sending:\n\n"
5144 "Signature failed: %s"), privacy_get_error());
5145 } else if (val == -2 && errno != 0) {
5146 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5148 alertpanel_error(_("Could not queue message for sending."));
5153 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5154 if (discard_window) {
5155 compose->sending = FALSE;
5156 compose_close(compose);
5157 /* No more compose access in the normal codepath
5158 * after this point! */
5163 alertpanel_error(_("The message was queued but could not be "
5164 "sent.\nUse \"Send queued messages\" from "
5165 "the main window to retry."));
5166 if (!discard_window) {
5173 if (msgpath == NULL) {
5174 msgpath = folder_item_fetch_msg(folder, msgnum);
5175 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5178 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5179 claws_unlink(msgpath);
5182 if (!discard_window) {
5184 if (!queued_removed)
5185 folder_item_remove_msg(folder, msgnum);
5186 folder_item_scan(folder);
5188 /* make sure we delete that */
5189 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5191 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5192 folder_item_remove_msg(folder, tmp->msgnum);
5193 procmsg_msginfo_free(tmp);
5200 if (!queued_removed)
5201 folder_item_remove_msg(folder, msgnum);
5202 folder_item_scan(folder);
5204 /* make sure we delete that */
5205 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5207 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5208 folder_item_remove_msg(folder, tmp->msgnum);
5209 procmsg_msginfo_free(tmp);
5212 if (!discard_window) {
5213 compose->sending = FALSE;
5214 compose_allow_user_actions (compose, TRUE);
5215 compose_close(compose);
5219 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5220 "the main window to retry."), errstr);
5223 alertpanel_error_log(_("The message was queued but could not be "
5224 "sent.\nUse \"Send queued messages\" from "
5225 "the main window to retry."));
5227 if (!discard_window) {
5236 toolbar_main_set_sensitive(mainwin);
5237 main_window_set_menu_sensitive(mainwin);
5243 compose_allow_user_actions (compose, TRUE);
5244 compose->sending = FALSE;
5245 compose->modified = TRUE;
5246 toolbar_main_set_sensitive(mainwin);
5247 main_window_set_menu_sensitive(mainwin);
5252 static gboolean compose_use_attach(Compose *compose)
5254 GtkTreeModel *model = gtk_tree_view_get_model
5255 (GTK_TREE_VIEW(compose->attach_clist));
5256 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5259 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5262 gchar buf[BUFFSIZE];
5264 gboolean first_to_address;
5265 gboolean first_cc_address;
5267 ComposeHeaderEntry *headerentry;
5268 const gchar *headerentryname;
5269 const gchar *cc_hdr;
5270 const gchar *to_hdr;
5271 gboolean err = FALSE;
5273 debug_print("Writing redirect header\n");
5275 cc_hdr = prefs_common_translated_header_name("Cc:");
5276 to_hdr = prefs_common_translated_header_name("To:");
5278 first_to_address = TRUE;
5279 for (list = compose->header_list; list; list = list->next) {
5280 headerentry = ((ComposeHeaderEntry *)list->data);
5281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5283 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5284 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5285 Xstrdup_a(str, entstr, return -1);
5287 if (str[0] != '\0') {
5288 compose_convert_header
5289 (compose, buf, sizeof(buf), str,
5290 strlen("Resent-To") + 2, TRUE);
5292 if (first_to_address) {
5293 err |= (fprintf(fp, "Resent-To: ") < 0);
5294 first_to_address = FALSE;
5296 err |= (fprintf(fp, ",") < 0);
5298 err |= (fprintf(fp, "%s", buf) < 0);
5302 if (!first_to_address) {
5303 err |= (fprintf(fp, "\n") < 0);
5306 first_cc_address = TRUE;
5307 for (list = compose->header_list; list; list = list->next) {
5308 headerentry = ((ComposeHeaderEntry *)list->data);
5309 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5311 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5312 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5313 Xstrdup_a(str, strg, return -1);
5315 if (str[0] != '\0') {
5316 compose_convert_header
5317 (compose, buf, sizeof(buf), str,
5318 strlen("Resent-Cc") + 2, TRUE);
5320 if (first_cc_address) {
5321 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5322 first_cc_address = FALSE;
5324 err |= (fprintf(fp, ",") < 0);
5326 err |= (fprintf(fp, "%s", buf) < 0);
5330 if (!first_cc_address) {
5331 err |= (fprintf(fp, "\n") < 0);
5334 return (err ? -1:0);
5337 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5339 gchar buf[BUFFSIZE];
5341 const gchar *entstr;
5342 /* struct utsname utsbuf; */
5343 gboolean err = FALSE;
5345 cm_return_val_if_fail(fp != NULL, -1);
5346 cm_return_val_if_fail(compose->account != NULL, -1);
5347 cm_return_val_if_fail(compose->account->address != NULL, -1);
5350 get_rfc822_date(buf, sizeof(buf));
5351 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5354 if (compose->account->name && *compose->account->name) {
5355 compose_convert_header
5356 (compose, buf, sizeof(buf), compose->account->name,
5357 strlen("From: "), TRUE);
5358 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5359 buf, compose->account->address) < 0);
5361 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5364 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5365 if (*entstr != '\0') {
5366 Xstrdup_a(str, entstr, return -1);
5369 compose_convert_header(compose, buf, sizeof(buf), str,
5370 strlen("Subject: "), FALSE);
5371 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5375 /* Resent-Message-ID */
5376 if (compose->account->set_domain && compose->account->domain) {
5377 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5378 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5379 g_snprintf(buf, sizeof(buf), "%s",
5380 strchr(compose->account->address, '@') ?
5381 strchr(compose->account->address, '@')+1 :
5382 compose->account->address);
5384 g_snprintf(buf, sizeof(buf), "%s", "");
5387 if (compose->account->gen_msgid) {
5389 if (compose->account->msgid_with_addr) {
5390 addr = compose->account->address;
5392 generate_msgid(buf, sizeof(buf), addr);
5393 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5395 g_free(compose->msgid);
5396 compose->msgid = g_strdup(buf);
5398 compose->msgid = NULL;
5401 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5404 /* separator between header and body */
5405 err |= (fputs("\n", fp) == EOF);
5407 return (err ? -1:0);
5410 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5414 gchar buf[BUFFSIZE];
5416 gboolean skip = FALSE;
5417 gboolean err = FALSE;
5418 gchar *not_included[]={
5419 "Return-Path:", "Delivered-To:", "Received:",
5420 "Subject:", "X-UIDL:", "AF:",
5421 "NF:", "PS:", "SRH:",
5422 "SFN:", "DSR:", "MID:",
5423 "CFG:", "PT:", "S:",
5424 "RQ:", "SSV:", "NSV:",
5425 "SSH:", "R:", "MAID:",
5426 "NAID:", "RMID:", "FMID:",
5427 "SCF:", "RRCPT:", "NG:",
5428 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5429 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5430 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5431 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5432 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5435 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5436 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5440 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5442 for (i = 0; not_included[i] != NULL; i++) {
5443 if (g_ascii_strncasecmp(buf, not_included[i],
5444 strlen(not_included[i])) == 0) {
5451 if (fputs(buf, fdest) == -1)
5454 if (!prefs_common.redirect_keep_from) {
5455 if (g_ascii_strncasecmp(buf, "From:",
5456 strlen("From:")) == 0) {
5457 err |= (fputs(" (by way of ", fdest) == EOF);
5458 if (compose->account->name
5459 && *compose->account->name) {
5460 compose_convert_header
5461 (compose, buf, sizeof(buf),
5462 compose->account->name,
5465 err |= (fprintf(fdest, "%s <%s>",
5467 compose->account->address) < 0);
5469 err |= (fprintf(fdest, "%s",
5470 compose->account->address) < 0);
5471 err |= (fputs(")", fdest) == EOF);
5475 if (fputs("\n", fdest) == -1)
5482 if (compose_redirect_write_headers(compose, fdest))
5485 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5486 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5499 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5501 GtkTextBuffer *buffer;
5502 GtkTextIter start, end;
5505 const gchar *out_codeset;
5506 EncodingType encoding = ENC_UNKNOWN;
5507 MimeInfo *mimemsg, *mimetext;
5509 const gchar *src_codeset = CS_INTERNAL;
5510 gchar *from_addr = NULL;
5511 gchar *from_name = NULL;
5513 if (action == COMPOSE_WRITE_FOR_SEND)
5514 attach_parts = TRUE;
5516 /* create message MimeInfo */
5517 mimemsg = procmime_mimeinfo_new();
5518 mimemsg->type = MIMETYPE_MESSAGE;
5519 mimemsg->subtype = g_strdup("rfc822");
5520 mimemsg->content = MIMECONTENT_MEM;
5521 mimemsg->tmp = TRUE; /* must free content later */
5522 mimemsg->data.mem = compose_get_header(compose);
5524 /* Create text part MimeInfo */
5525 /* get all composed text */
5526 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5527 gtk_text_buffer_get_start_iter(buffer, &start);
5528 gtk_text_buffer_get_end_iter(buffer, &end);
5529 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5531 out_codeset = conv_get_charset_str(compose->out_encoding);
5533 if (!out_codeset && is_ascii_str(chars)) {
5534 out_codeset = CS_US_ASCII;
5535 } else if (prefs_common.outgoing_fallback_to_ascii &&
5536 is_ascii_str(chars)) {
5537 out_codeset = CS_US_ASCII;
5538 encoding = ENC_7BIT;
5542 gchar *test_conv_global_out = NULL;
5543 gchar *test_conv_reply = NULL;
5545 /* automatic mode. be automatic. */
5546 codeconv_set_strict(TRUE);
5548 out_codeset = conv_get_outgoing_charset_str();
5550 debug_print("trying to convert to %s\n", out_codeset);
5551 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5554 if (!test_conv_global_out && compose->orig_charset
5555 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5556 out_codeset = compose->orig_charset;
5557 debug_print("failure; trying to convert to %s\n", out_codeset);
5558 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5561 if (!test_conv_global_out && !test_conv_reply) {
5563 out_codeset = CS_INTERNAL;
5564 debug_print("failure; finally using %s\n", out_codeset);
5566 g_free(test_conv_global_out);
5567 g_free(test_conv_reply);
5568 codeconv_set_strict(FALSE);
5571 if (encoding == ENC_UNKNOWN) {
5572 if (prefs_common.encoding_method == CTE_BASE64)
5573 encoding = ENC_BASE64;
5574 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5575 encoding = ENC_QUOTED_PRINTABLE;
5576 else if (prefs_common.encoding_method == CTE_8BIT)
5577 encoding = ENC_8BIT;
5579 encoding = procmime_get_encoding_for_charset(out_codeset);
5582 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5583 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5585 if (action == COMPOSE_WRITE_FOR_SEND) {
5586 codeconv_set_strict(TRUE);
5587 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5588 codeconv_set_strict(FALSE);
5594 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5595 "to the specified %s charset.\n"
5596 "Send it as %s?"), out_codeset, src_codeset);
5597 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5598 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5601 if (aval != G_ALERTALTERNATE) {
5606 out_codeset = src_codeset;
5612 out_codeset = src_codeset;
5617 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5618 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5619 strstr(buf, "\nFrom ") != NULL) {
5620 encoding = ENC_QUOTED_PRINTABLE;
5624 mimetext = procmime_mimeinfo_new();
5625 mimetext->content = MIMECONTENT_MEM;
5626 mimetext->tmp = TRUE; /* must free content later */
5627 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5628 * and free the data, which we need later. */
5629 mimetext->data.mem = g_strdup(buf);
5630 mimetext->type = MIMETYPE_TEXT;
5631 mimetext->subtype = g_strdup("plain");
5632 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5633 g_strdup(out_codeset));
5635 /* protect trailing spaces when signing message */
5636 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5637 privacy_system_can_sign(compose->privacy_system)) {
5638 encoding = ENC_QUOTED_PRINTABLE;
5641 debug_print("main text: %zd bytes encoded as %s in %d\n",
5642 strlen(buf), out_codeset, encoding);
5644 /* check for line length limit */
5645 if (action == COMPOSE_WRITE_FOR_SEND &&
5646 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5647 check_line_length(buf, 1000, &line) < 0) {
5651 msg = g_strdup_printf
5652 (_("Line %d exceeds the line length limit (998 bytes).\n"
5653 "The contents of the message might be broken on the way to the delivery.\n"
5655 "Send it anyway?"), line + 1);
5656 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5658 if (aval != G_ALERTALTERNATE) {
5664 if (encoding != ENC_UNKNOWN)
5665 procmime_encode_content(mimetext, encoding);
5667 /* append attachment parts */
5668 if (compose_use_attach(compose) && attach_parts) {
5669 MimeInfo *mimempart;
5670 gchar *boundary = NULL;
5671 mimempart = procmime_mimeinfo_new();
5672 mimempart->content = MIMECONTENT_EMPTY;
5673 mimempart->type = MIMETYPE_MULTIPART;
5674 mimempart->subtype = g_strdup("mixed");
5678 boundary = generate_mime_boundary(NULL);
5679 } while (strstr(buf, boundary) != NULL);
5681 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5684 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5686 g_node_append(mimempart->node, mimetext->node);
5687 g_node_append(mimemsg->node, mimempart->node);
5689 if (compose_add_attachments(compose, mimempart) < 0)
5692 g_node_append(mimemsg->node, mimetext->node);
5696 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5697 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5698 /* extract name and address */
5699 if (strstr(spec, " <") && strstr(spec, ">")) {
5700 from_addr = g_strdup(strrchr(spec, '<')+1);
5701 *(strrchr(from_addr, '>')) = '\0';
5702 from_name = g_strdup(spec);
5703 *(strrchr(from_name, '<')) = '\0';
5710 /* sign message if sending */
5711 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5712 privacy_system_can_sign(compose->privacy_system))
5713 if (!privacy_sign(compose->privacy_system, mimemsg,
5714 compose->account, from_addr)) {
5721 procmime_write_mimeinfo(mimemsg, fp);
5723 procmime_mimeinfo_free_all(mimemsg);
5728 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5730 GtkTextBuffer *buffer;
5731 GtkTextIter start, end;
5736 if ((fp = g_fopen(file, "wb")) == NULL) {
5737 FILE_OP_ERROR(file, "fopen");
5741 /* chmod for security */
5742 if (change_file_mode_rw(fp, file) < 0) {
5743 FILE_OP_ERROR(file, "chmod");
5744 g_warning("can't change file mode\n");
5747 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5748 gtk_text_buffer_get_start_iter(buffer, &start);
5749 gtk_text_buffer_get_end_iter(buffer, &end);
5750 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5752 chars = conv_codeset_strdup
5753 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5762 len = strlen(chars);
5763 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5764 FILE_OP_ERROR(file, "fwrite");
5773 if (fclose(fp) == EOF) {
5774 FILE_OP_ERROR(file, "fclose");
5781 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5784 MsgInfo *msginfo = compose->targetinfo;
5786 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5787 if (!msginfo) return -1;
5789 if (!force && MSG_IS_LOCKED(msginfo->flags))
5792 item = msginfo->folder;
5793 cm_return_val_if_fail(item != NULL, -1);
5795 if (procmsg_msg_exist(msginfo) &&
5796 (folder_has_parent_of_type(item, F_QUEUE) ||
5797 folder_has_parent_of_type(item, F_DRAFT)
5798 || msginfo == compose->autosaved_draft)) {
5799 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5800 g_warning("can't remove the old message\n");
5803 debug_print("removed reedit target %d\n", msginfo->msgnum);
5810 static void compose_remove_draft(Compose *compose)
5813 MsgInfo *msginfo = compose->targetinfo;
5814 drafts = account_get_special_folder(compose->account, F_DRAFT);
5816 if (procmsg_msg_exist(msginfo)) {
5817 folder_item_remove_msg(drafts, msginfo->msgnum);
5822 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5823 gboolean remove_reedit_target)
5825 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5828 static gboolean compose_warn_encryption(Compose *compose)
5830 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5831 AlertValue val = G_ALERTALTERNATE;
5833 if (warning == NULL)
5836 val = alertpanel_full(_("Encryption warning"), warning,
5837 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5838 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5839 if (val & G_ALERTDISABLE) {
5840 val &= ~G_ALERTDISABLE;
5841 if (val == G_ALERTALTERNATE)
5842 privacy_inhibit_encrypt_warning(compose->privacy_system,
5846 if (val == G_ALERTALTERNATE) {
5853 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5854 gchar **msgpath, gboolean check_subject,
5855 gboolean remove_reedit_target)
5862 PrefsAccount *mailac = NULL, *newsac = NULL;
5863 gboolean err = FALSE;
5865 debug_print("queueing message...\n");
5866 cm_return_val_if_fail(compose->account != NULL, -1);
5868 if (compose_check_entries(compose, check_subject) == FALSE) {
5869 if (compose->batch) {
5870 gtk_widget_show_all(compose->window);
5875 if (!compose->to_list && !compose->newsgroup_list) {
5876 g_warning("can't get recipient list.");
5880 if (compose->to_list) {
5881 if (compose->account->protocol != A_NNTP)
5882 mailac = compose->account;
5883 else if (cur_account && cur_account->protocol != A_NNTP)
5884 mailac = cur_account;
5885 else if (!(mailac = compose_current_mail_account())) {
5886 alertpanel_error(_("No account for sending mails available!"));
5891 if (compose->newsgroup_list) {
5892 if (compose->account->protocol == A_NNTP)
5893 newsac = compose->account;
5895 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5900 /* write queue header */
5901 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5902 G_DIR_SEPARATOR, compose, (guint) rand());
5903 debug_print("queuing to %s\n", tmp);
5904 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5905 FILE_OP_ERROR(tmp, "fopen");
5910 if (change_file_mode_rw(fp, tmp) < 0) {
5911 FILE_OP_ERROR(tmp, "chmod");
5912 g_warning("can't change file mode\n");
5915 /* queueing variables */
5916 err |= (fprintf(fp, "AF:\n") < 0);
5917 err |= (fprintf(fp, "NF:0\n") < 0);
5918 err |= (fprintf(fp, "PS:10\n") < 0);
5919 err |= (fprintf(fp, "SRH:1\n") < 0);
5920 err |= (fprintf(fp, "SFN:\n") < 0);
5921 err |= (fprintf(fp, "DSR:\n") < 0);
5923 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5925 err |= (fprintf(fp, "MID:\n") < 0);
5926 err |= (fprintf(fp, "CFG:\n") < 0);
5927 err |= (fprintf(fp, "PT:0\n") < 0);
5928 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5929 err |= (fprintf(fp, "RQ:\n") < 0);
5931 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5933 err |= (fprintf(fp, "SSV:\n") < 0);
5935 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5937 err |= (fprintf(fp, "NSV:\n") < 0);
5938 err |= (fprintf(fp, "SSH:\n") < 0);
5939 /* write recepient list */
5940 if (compose->to_list) {
5941 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5942 for (cur = compose->to_list->next; cur != NULL;
5944 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5945 err |= (fprintf(fp, "\n") < 0);
5947 /* write newsgroup list */
5948 if (compose->newsgroup_list) {
5949 err |= (fprintf(fp, "NG:") < 0);
5950 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5951 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5952 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5953 err |= (fprintf(fp, "\n") < 0);
5955 /* Sylpheed account IDs */
5957 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5959 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5962 if (compose->privacy_system != NULL) {
5963 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5964 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5965 if (compose->use_encryption) {
5967 if (!compose_warn_encryption(compose)) {
5973 if (mailac && mailac->encrypt_to_self) {
5974 GSList *tmp_list = g_slist_copy(compose->to_list);
5975 tmp_list = g_slist_append(tmp_list, compose->account->address);
5976 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5977 g_slist_free(tmp_list);
5979 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5981 if (encdata != NULL) {
5982 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5983 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5984 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5986 } /* else we finally dont want to encrypt */
5988 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5989 /* and if encdata was null, it means there's been a problem in
5992 g_warning("failed to write queue message");
6002 /* Save copy folder */
6003 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
6004 gchar *savefolderid;
6006 savefolderid = compose_get_save_to(compose);
6007 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
6008 g_free(savefolderid);
6010 /* Save copy folder */
6011 if (compose->return_receipt) {
6012 err |= (fprintf(fp, "RRCPT:1\n") < 0);
6014 /* Message-ID of message replying to */
6015 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
6016 gchar *folderid = NULL;
6018 if (compose->replyinfo->folder)
6019 folderid = folder_item_get_identifier(compose->replyinfo->folder);
6020 if (folderid == NULL)
6021 folderid = g_strdup("NULL");
6023 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
6026 /* Message-ID of message forwarding to */
6027 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
6028 gchar *folderid = NULL;
6030 if (compose->fwdinfo->folder)
6031 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
6032 if (folderid == NULL)
6033 folderid = g_strdup("NULL");
6035 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
6039 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
6040 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
6042 /* end of headers */
6043 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
6045 if (compose->redirect_filename != NULL) {
6046 if (compose_redirect_write_to_file(compose, fp) < 0) {
6054 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
6058 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6062 g_warning("failed to write queue message\n");
6068 if (fclose(fp) == EOF) {
6069 FILE_OP_ERROR(tmp, "fclose");
6075 if (item && *item) {
6078 queue = account_get_special_folder(compose->account, F_QUEUE);
6081 g_warning("can't find queue folder\n");
6086 folder_item_scan(queue);
6087 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6088 g_warning("can't queue the message\n");
6094 if (msgpath == NULL) {
6100 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6101 compose_remove_reedit_target(compose, FALSE);
6104 if ((msgnum != NULL) && (item != NULL)) {
6112 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6115 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6117 struct stat statbuf;
6118 gchar *type, *subtype;
6119 GtkTreeModel *model;
6122 model = gtk_tree_view_get_model(tree_view);
6124 if (!gtk_tree_model_get_iter_first(model, &iter))
6127 gtk_tree_model_get(model, &iter,
6131 if (!is_file_exist(ainfo->file)) {
6132 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6133 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6134 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6136 if (val == G_ALERTDEFAULT) {
6141 if (g_stat(ainfo->file, &statbuf) < 0)
6144 mimepart = procmime_mimeinfo_new();
6145 mimepart->content = MIMECONTENT_FILE;
6146 mimepart->data.filename = g_strdup(ainfo->file);
6147 mimepart->tmp = FALSE; /* or we destroy our attachment */
6148 mimepart->offset = 0;
6149 mimepart->length = statbuf.st_size;
6151 type = g_strdup(ainfo->content_type);
6153 if (!strchr(type, '/')) {
6155 type = g_strdup("application/octet-stream");
6158 subtype = strchr(type, '/') + 1;
6159 *(subtype - 1) = '\0';
6160 mimepart->type = procmime_get_media_type(type);
6161 mimepart->subtype = g_strdup(subtype);
6164 if (mimepart->type == MIMETYPE_MESSAGE &&
6165 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6166 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6167 } else if (mimepart->type == MIMETYPE_TEXT) {
6168 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6169 /* Text parts with no name come from multipart/alternative
6170 * forwards. Make sure the recipient won't look at the
6171 * original HTML part by mistake. */
6172 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6173 ainfo->name = g_strdup_printf(_("Original %s part"),
6177 g_hash_table_insert(mimepart->typeparameters,
6178 g_strdup("charset"), g_strdup(ainfo->charset));
6180 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6181 if (mimepart->type == MIMETYPE_APPLICATION &&
6182 !strcmp2(mimepart->subtype, "octet-stream"))
6183 g_hash_table_insert(mimepart->typeparameters,
6184 g_strdup("name"), g_strdup(ainfo->name));
6185 g_hash_table_insert(mimepart->dispositionparameters,
6186 g_strdup("filename"), g_strdup(ainfo->name));
6187 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6190 if (mimepart->type == MIMETYPE_MESSAGE
6191 || mimepart->type == MIMETYPE_MULTIPART)
6192 ainfo->encoding = ENC_BINARY;
6193 else if (compose->use_signing) {
6194 if (ainfo->encoding == ENC_7BIT)
6195 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6196 else if (ainfo->encoding == ENC_8BIT)
6197 ainfo->encoding = ENC_BASE64;
6202 procmime_encode_content(mimepart, ainfo->encoding);
6204 g_node_append(parent->node, mimepart->node);
6205 } while (gtk_tree_model_iter_next(model, &iter));
6210 static gchar *compose_quote_list_of_addresses(gchar *str)
6212 GSList *list = NULL, *item = NULL;
6213 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6215 list = address_list_append_with_comments(list, str);
6216 for (item = list; item != NULL; item = item->next) {
6217 gchar *spec = item->data;
6218 gchar *endofname = strstr(spec, " <");
6219 if (endofname != NULL) {
6222 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6223 qqname = escape_internal_quotes(qname, '"');
6225 if (*qname != *spec || qqname != qname) { /* has been quoted, compute new */
6226 gchar *addr = g_strdup(endofname);
6227 gchar *name = (qqname != qname)? qqname: g_strdup(qname);
6228 faddr = g_strconcat(name, addr, NULL);
6231 debug_print("new auto-quoted address: '%s'", faddr);
6235 result = g_strdup((faddr != NULL)? faddr: spec);
6237 result = g_strconcat(result,
6239 (faddr != NULL)? faddr: spec,
6242 if (faddr != NULL) {
6247 slist_free_strings_full(list);
6252 #define IS_IN_CUSTOM_HEADER(header) \
6253 (compose->account->add_customhdr && \
6254 custom_header_find(compose->account->customhdr_list, header) != NULL)
6256 static void compose_add_headerfield_from_headerlist(Compose *compose,
6258 const gchar *fieldname,
6259 const gchar *seperator)
6261 gchar *str, *fieldname_w_colon;
6262 gboolean add_field = FALSE;
6264 ComposeHeaderEntry *headerentry;
6265 const gchar *headerentryname;
6266 const gchar *trans_fieldname;
6269 if (IS_IN_CUSTOM_HEADER(fieldname))
6272 debug_print("Adding %s-fields\n", fieldname);
6274 fieldstr = g_string_sized_new(64);
6276 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6277 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6279 for (list = compose->header_list; list; list = list->next) {
6280 headerentry = ((ComposeHeaderEntry *)list->data);
6281 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6283 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6284 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6286 str = compose_quote_list_of_addresses(ustr);
6288 if (str != NULL && str[0] != '\0') {
6290 g_string_append(fieldstr, seperator);
6291 g_string_append(fieldstr, str);
6300 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6301 compose_convert_header
6302 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6303 strlen(fieldname) + 2, TRUE);
6304 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6308 g_free(fieldname_w_colon);
6309 g_string_free(fieldstr, TRUE);
6314 static gchar *compose_get_manual_headers_info(Compose *compose)
6316 GString *sh_header = g_string_new(" ");
6318 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6320 for (list = compose->header_list; list; list = list->next) {
6321 ComposeHeaderEntry *headerentry;
6324 gchar *headername_wcolon;
6325 const gchar *headername_trans;
6327 gboolean standard_header = FALSE;
6329 headerentry = ((ComposeHeaderEntry *)list->data);
6331 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6333 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6338 if (!strstr(tmp, ":")) {
6339 headername_wcolon = g_strconcat(tmp, ":", NULL);
6340 headername = g_strdup(tmp);
6342 headername_wcolon = g_strdup(tmp);
6343 headername = g_strdup(strtok(tmp, ":"));
6347 string = std_headers;
6348 while (*string != NULL) {
6349 headername_trans = prefs_common_translated_header_name(*string);
6350 if (!strcmp(headername_trans, headername_wcolon))
6351 standard_header = TRUE;
6354 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6355 g_string_append_printf(sh_header, "%s ", headername);
6357 g_free(headername_wcolon);
6359 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6360 return g_string_free(sh_header, FALSE);
6363 static gchar *compose_get_header(Compose *compose)
6365 gchar buf[BUFFSIZE];
6366 const gchar *entry_str;
6370 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6372 gchar *from_name = NULL, *from_address = NULL;
6375 cm_return_val_if_fail(compose->account != NULL, NULL);
6376 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6378 header = g_string_sized_new(64);
6381 get_rfc822_date(buf, sizeof(buf));
6382 g_string_append_printf(header, "Date: %s\n", buf);
6386 if (compose->account->name && *compose->account->name) {
6388 QUOTE_IF_REQUIRED(buf, compose->account->name);
6389 tmp = g_strdup_printf("%s <%s>",
6390 buf, compose->account->address);
6392 tmp = g_strdup_printf("%s",
6393 compose->account->address);
6395 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6396 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6398 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6399 from_address = g_strdup(compose->account->address);
6401 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6402 /* extract name and address */
6403 if (strstr(spec, " <") && strstr(spec, ">")) {
6404 from_address = g_strdup(strrchr(spec, '<')+1);
6405 *(strrchr(from_address, '>')) = '\0';
6406 from_name = g_strdup(spec);
6407 *(strrchr(from_name, '<')) = '\0';
6410 from_address = g_strdup(spec);
6417 if (from_name && *from_name) {
6419 compose_convert_header
6420 (compose, buf, sizeof(buf), from_name,
6421 strlen("From: "), TRUE);
6422 QUOTE_IF_REQUIRED(name, buf);
6423 qname = escape_internal_quotes(name, '"');
6425 g_string_append_printf(header, "From: %s <%s>\n",
6426 qname, from_address);
6430 g_string_append_printf(header, "From: %s\n", from_address);
6433 g_free(from_address);
6436 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6439 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6442 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6446 * If this account is a NNTP account remove Bcc header from
6447 * message body since it otherwise will be publicly shown
6449 if (compose->account->protocol != A_NNTP)
6450 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6453 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6455 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6458 compose_convert_header(compose, buf, sizeof(buf), str,
6459 strlen("Subject: "), FALSE);
6460 g_string_append_printf(header, "Subject: %s\n", buf);
6466 if (compose->account->set_domain && compose->account->domain) {
6467 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6468 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6469 g_snprintf(buf, sizeof(buf), "%s",
6470 strchr(compose->account->address, '@') ?
6471 strchr(compose->account->address, '@')+1 :
6472 compose->account->address);
6474 g_snprintf(buf, sizeof(buf), "%s", "");
6477 if (compose->account->gen_msgid) {
6479 if (compose->account->msgid_with_addr) {
6480 addr = compose->account->address;
6482 generate_msgid(buf, sizeof(buf), addr);
6483 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6485 g_free(compose->msgid);
6486 compose->msgid = g_strdup(buf);
6488 compose->msgid = NULL;
6491 if (compose->remove_references == FALSE) {
6493 if (compose->inreplyto && compose->to_list)
6494 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6497 if (compose->references)
6498 g_string_append_printf(header, "References: %s\n", compose->references);
6502 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6505 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6508 if (compose->account->organization &&
6509 strlen(compose->account->organization) &&
6510 !IS_IN_CUSTOM_HEADER("Organization")) {
6511 compose_convert_header(compose, buf, sizeof(buf),
6512 compose->account->organization,
6513 strlen("Organization: "), FALSE);
6514 g_string_append_printf(header, "Organization: %s\n", buf);
6517 /* Program version and system info */
6518 if (compose->account->gen_xmailer &&
6519 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6520 !compose->newsgroup_list) {
6521 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6523 gtk_major_version, gtk_minor_version, gtk_micro_version,
6526 if (compose->account->gen_xmailer &&
6527 g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6528 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6530 gtk_major_version, gtk_minor_version, gtk_micro_version,
6534 /* custom headers */
6535 if (compose->account->add_customhdr) {
6538 for (cur = compose->account->customhdr_list; cur != NULL;
6540 CustomHeader *chdr = (CustomHeader *)cur->data;
6542 if (custom_header_is_allowed(chdr->name)
6543 && chdr->value != NULL
6544 && *(chdr->value) != '\0') {
6545 compose_convert_header
6546 (compose, buf, sizeof(buf),
6548 strlen(chdr->name) + 2, FALSE);
6549 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6554 /* Automatic Faces and X-Faces */
6555 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6556 g_string_append_printf(header, "X-Face: %s\n", buf);
6558 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6559 g_string_append_printf(header, "X-Face: %s\n", buf);
6561 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6562 g_string_append_printf(header, "Face: %s\n", buf);
6564 else if (get_default_face (buf, sizeof(buf)) == 0) {
6565 g_string_append_printf(header, "Face: %s\n", buf);
6569 switch (compose->priority) {
6570 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6571 "X-Priority: 1 (Highest)\n");
6573 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6574 "X-Priority: 2 (High)\n");
6576 case PRIORITY_NORMAL: break;
6577 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6578 "X-Priority: 4 (Low)\n");
6580 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6581 "X-Priority: 5 (Lowest)\n");
6583 default: debug_print("compose: priority unknown : %d\n",
6587 /* Request Return Receipt */
6588 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6589 if (compose->return_receipt) {
6590 if (compose->account->name
6591 && *compose->account->name) {
6592 compose_convert_header(compose, buf, sizeof(buf),
6593 compose->account->name,
6594 strlen("Disposition-Notification-To: "),
6596 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6598 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6602 /* get special headers */
6603 for (list = compose->header_list; list; list = list->next) {
6604 ComposeHeaderEntry *headerentry;
6607 gchar *headername_wcolon;
6608 const gchar *headername_trans;
6611 gboolean standard_header = FALSE;
6613 headerentry = ((ComposeHeaderEntry *)list->data);
6615 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6617 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6622 if (!strstr(tmp, ":")) {
6623 headername_wcolon = g_strconcat(tmp, ":", NULL);
6624 headername = g_strdup(tmp);
6626 headername_wcolon = g_strdup(tmp);
6627 headername = g_strdup(strtok(tmp, ":"));
6631 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6632 Xstrdup_a(headervalue, entry_str, return NULL);
6633 subst_char(headervalue, '\r', ' ');
6634 subst_char(headervalue, '\n', ' ');
6635 string = std_headers;
6636 while (*string != NULL) {
6637 headername_trans = prefs_common_translated_header_name(*string);
6638 if (!strcmp(headername_trans, headername_wcolon))
6639 standard_header = TRUE;
6642 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6643 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6646 g_free(headername_wcolon);
6650 g_string_free(header, FALSE);
6655 #undef IS_IN_CUSTOM_HEADER
6657 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6658 gint header_len, gboolean addr_field)
6660 gchar *tmpstr = NULL;
6661 const gchar *out_codeset = NULL;
6663 cm_return_if_fail(src != NULL);
6664 cm_return_if_fail(dest != NULL);
6666 if (len < 1) return;
6668 tmpstr = g_strdup(src);
6670 subst_char(tmpstr, '\n', ' ');
6671 subst_char(tmpstr, '\r', ' ');
6674 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6675 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6676 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6681 codeconv_set_strict(TRUE);
6682 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6683 conv_get_charset_str(compose->out_encoding));
6684 codeconv_set_strict(FALSE);
6686 if (!dest || *dest == '\0') {
6687 gchar *test_conv_global_out = NULL;
6688 gchar *test_conv_reply = NULL;
6690 /* automatic mode. be automatic. */
6691 codeconv_set_strict(TRUE);
6693 out_codeset = conv_get_outgoing_charset_str();
6695 debug_print("trying to convert to %s\n", out_codeset);
6696 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6699 if (!test_conv_global_out && compose->orig_charset
6700 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6701 out_codeset = compose->orig_charset;
6702 debug_print("failure; trying to convert to %s\n", out_codeset);
6703 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6706 if (!test_conv_global_out && !test_conv_reply) {
6708 out_codeset = CS_INTERNAL;
6709 debug_print("finally using %s\n", out_codeset);
6711 g_free(test_conv_global_out);
6712 g_free(test_conv_reply);
6713 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6715 codeconv_set_strict(FALSE);
6720 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6724 cm_return_if_fail(user_data != NULL);
6726 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6727 g_strstrip(address);
6728 if (*address != '\0') {
6729 gchar *name = procheader_get_fromname(address);
6730 extract_address(address);
6731 #ifndef USE_NEW_ADDRBOOK
6732 addressbook_add_contact(name, address, NULL, NULL);
6734 debug_print("%s: %s\n", name, address);
6735 if (addressadd_selection(name, address, NULL, NULL)) {
6736 debug_print( "addressbook_add_contact - added\n" );
6743 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6745 GtkWidget *menuitem;
6748 cm_return_if_fail(menu != NULL);
6749 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6751 menuitem = gtk_separator_menu_item_new();
6752 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6753 gtk_widget_show(menuitem);
6755 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6756 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6758 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6759 g_strstrip(address);
6760 if (*address == '\0') {
6761 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6764 g_signal_connect(G_OBJECT(menuitem), "activate",
6765 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6766 gtk_widget_show(menuitem);
6769 void compose_add_extra_header(gchar *header, GtkListStore *model)
6772 if (strcmp(header, "")) {
6773 COMBOBOX_ADD(model, header, COMPOSE_TO);
6777 void compose_add_extra_header_entries(GtkListStore *model)
6781 gchar buf[BUFFSIZE];
6784 if (extra_headers == NULL) {
6785 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6786 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6787 debug_print("extra headers file not found\n");
6788 goto extra_headers_done;
6790 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6791 lastc = strlen(buf) - 1; /* remove trailing control chars */
6792 while (lastc >= 0 && buf[lastc] != ':')
6793 buf[lastc--] = '\0';
6794 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6795 buf[lastc] = '\0'; /* remove trailing : for comparison */
6796 if (custom_header_is_allowed(buf)) {
6798 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6801 g_message("disallowed extra header line: %s\n", buf);
6805 g_message("invalid extra header line: %s\n", buf);
6811 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6812 extra_headers = g_slist_reverse(extra_headers);
6814 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6817 static void compose_create_header_entry(Compose *compose)
6819 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6826 const gchar *header = NULL;
6827 ComposeHeaderEntry *headerentry;
6828 gboolean standard_header = FALSE;
6829 GtkListStore *model;
6832 headerentry = g_new0(ComposeHeaderEntry, 1);
6834 /* Combo box model */
6835 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6836 #if !GTK_CHECK_VERSION(2, 24, 0)
6837 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6839 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6841 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6843 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6845 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6846 COMPOSE_NEWSGROUPS);
6847 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6849 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6850 COMPOSE_FOLLOWUPTO);
6851 compose_add_extra_header_entries(model);
6854 #if GTK_CHECK_VERSION(2, 24, 0)
6855 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6856 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6857 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6858 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6859 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6861 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6862 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6863 G_CALLBACK(compose_grab_focus_cb), compose);
6864 gtk_widget_show(combo);
6866 /* Putting only the combobox child into focus chain of its parent causes
6867 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6868 * This eliminates need to pres Tab twice in order to really get from the
6869 * combobox to next widget. */
6871 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6872 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6875 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6876 compose->header_nextrow, compose->header_nextrow+1,
6877 GTK_SHRINK, GTK_FILL, 0, 0);
6878 if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
6879 const gchar *last_header_entry = gtk_entry_get_text(
6880 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6882 while (*string != NULL) {
6883 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6884 standard_header = TRUE;
6887 if (standard_header)
6888 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6890 if (!compose->header_last || !standard_header) {
6891 switch(compose->account->protocol) {
6893 header = prefs_common_translated_header_name("Newsgroups:");
6896 header = prefs_common_translated_header_name("To:");
6901 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6903 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6904 G_CALLBACK(compose_grab_focus_cb), compose);
6906 /* Entry field with cleanup button */
6907 button = gtk_button_new();
6908 gtk_button_set_image(GTK_BUTTON(button),
6909 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6910 gtk_widget_show(button);
6911 CLAWS_SET_TIP(button,
6912 _("Delete entry contents"));
6913 entry = gtk_entry_new();
6914 gtk_widget_show(entry);
6915 CLAWS_SET_TIP(entry,
6916 _("Use <tab> to autocomplete from addressbook"));
6917 hbox = gtk_hbox_new (FALSE, 0);
6918 gtk_widget_show(hbox);
6919 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6920 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6921 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6922 compose->header_nextrow, compose->header_nextrow+1,
6923 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6925 g_signal_connect(G_OBJECT(entry), "key-press-event",
6926 G_CALLBACK(compose_headerentry_key_press_event_cb),
6928 g_signal_connect(G_OBJECT(entry), "changed",
6929 G_CALLBACK(compose_headerentry_changed_cb),
6931 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6932 G_CALLBACK(compose_grab_focus_cb), compose);
6934 g_signal_connect(G_OBJECT(button), "clicked",
6935 G_CALLBACK(compose_headerentry_button_clicked_cb),
6939 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6940 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6941 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6942 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6943 G_CALLBACK(compose_header_drag_received_cb),
6945 g_signal_connect(G_OBJECT(entry), "drag-drop",
6946 G_CALLBACK(compose_drag_drop),
6948 g_signal_connect(G_OBJECT(entry), "populate-popup",
6949 G_CALLBACK(compose_entry_popup_extend),
6952 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6954 headerentry->compose = compose;
6955 headerentry->combo = combo;
6956 headerentry->entry = entry;
6957 headerentry->button = button;
6958 headerentry->hbox = hbox;
6959 headerentry->headernum = compose->header_nextrow;
6960 headerentry->type = PREF_NONE;
6962 compose->header_nextrow++;
6963 compose->header_last = headerentry;
6964 compose->header_list =
6965 g_slist_append(compose->header_list,
6969 static void compose_add_header_entry(Compose *compose, const gchar *header,
6970 gchar *text, ComposePrefType pref_type)
6972 ComposeHeaderEntry *last_header = compose->header_last;
6973 gchar *tmp = g_strdup(text), *email;
6974 gboolean replyto_hdr;
6976 replyto_hdr = (!strcasecmp(header,
6977 prefs_common_translated_header_name("Reply-To:")) ||
6979 prefs_common_translated_header_name("Followup-To:")) ||
6981 prefs_common_translated_header_name("In-Reply-To:")));
6983 extract_address(tmp);
6984 email = g_utf8_strdown(tmp, -1);
6986 if (replyto_hdr == FALSE &&
6987 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6989 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6990 header, text, (gint) pref_type);
6996 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6997 gtk_entry_set_text(GTK_ENTRY(
6998 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
7000 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
7001 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
7002 last_header->type = pref_type;
7004 if (replyto_hdr == FALSE)
7005 g_hash_table_insert(compose->email_hashtable, email,
7006 GUINT_TO_POINTER(1));
7013 static void compose_destroy_headerentry(Compose *compose,
7014 ComposeHeaderEntry *headerentry)
7016 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
7019 extract_address(text);
7020 email = g_utf8_strdown(text, -1);
7021 g_hash_table_remove(compose->email_hashtable, email);
7025 gtk_widget_destroy(headerentry->combo);
7026 gtk_widget_destroy(headerentry->entry);
7027 gtk_widget_destroy(headerentry->button);
7028 gtk_widget_destroy(headerentry->hbox);
7029 g_free(headerentry);
7032 static void compose_remove_header_entries(Compose *compose)
7035 for (list = compose->header_list; list; list = list->next)
7036 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
7038 compose->header_last = NULL;
7039 g_slist_free(compose->header_list);
7040 compose->header_list = NULL;
7041 compose->header_nextrow = 1;
7042 compose_create_header_entry(compose);
7045 static GtkWidget *compose_create_header(Compose *compose)
7047 GtkWidget *from_optmenu_hbox;
7048 GtkWidget *header_scrolledwin_main;
7049 GtkWidget *header_table_main;
7050 GtkWidget *header_scrolledwin;
7051 GtkWidget *header_table;
7053 /* parent with account selection and from header */
7054 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
7055 gtk_widget_show(header_scrolledwin_main);
7056 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7058 header_table_main = gtk_table_new(2, 2, FALSE);
7059 gtk_widget_show(header_table_main);
7060 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
7061 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
7062 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
7064 from_optmenu_hbox = compose_account_option_menu_create(compose);
7065 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
7066 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
7068 /* child with header labels and entries */
7069 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7070 gtk_widget_show(header_scrolledwin);
7071 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
7073 header_table = gtk_table_new(2, 2, FALSE);
7074 gtk_widget_show(header_table);
7075 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
7076 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
7077 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
7079 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
7080 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
7082 compose->header_table = header_table;
7083 compose->header_list = NULL;
7084 compose->header_nextrow = 0;
7086 compose_create_header_entry(compose);
7088 compose->table = NULL;
7090 return header_scrolledwin_main;
7093 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7095 Compose *compose = (Compose *)data;
7096 GdkEventButton event;
7099 event.time = gtk_get_current_event_time();
7101 return attach_button_pressed(compose->attach_clist, &event, compose);
7104 static GtkWidget *compose_create_attach(Compose *compose)
7106 GtkWidget *attach_scrwin;
7107 GtkWidget *attach_clist;
7109 GtkListStore *store;
7110 GtkCellRenderer *renderer;
7111 GtkTreeViewColumn *column;
7112 GtkTreeSelection *selection;
7114 /* attachment list */
7115 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7116 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7117 GTK_POLICY_AUTOMATIC,
7118 GTK_POLICY_AUTOMATIC);
7119 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7121 store = gtk_list_store_new(N_ATTACH_COLS,
7127 G_TYPE_AUTO_POINTER,
7129 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7130 (GTK_TREE_MODEL(store)));
7131 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7132 g_object_unref(store);
7134 renderer = gtk_cell_renderer_text_new();
7135 column = gtk_tree_view_column_new_with_attributes
7136 (_("Mime type"), renderer, "text",
7137 COL_MIMETYPE, NULL);
7138 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7140 renderer = gtk_cell_renderer_text_new();
7141 column = gtk_tree_view_column_new_with_attributes
7142 (_("Size"), renderer, "text",
7144 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7146 renderer = gtk_cell_renderer_text_new();
7147 column = gtk_tree_view_column_new_with_attributes
7148 (_("Name"), renderer, "text",
7150 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7152 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7153 prefs_common.use_stripes_everywhere);
7154 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7155 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7157 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7158 G_CALLBACK(attach_selected), compose);
7159 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7160 G_CALLBACK(attach_button_pressed), compose);
7161 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7162 G_CALLBACK(popup_attach_button_pressed), compose);
7163 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7164 G_CALLBACK(attach_key_pressed), compose);
7167 gtk_drag_dest_set(attach_clist,
7168 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7169 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7170 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7171 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7172 G_CALLBACK(compose_attach_drag_received_cb),
7174 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7175 G_CALLBACK(compose_drag_drop),
7178 compose->attach_scrwin = attach_scrwin;
7179 compose->attach_clist = attach_clist;
7181 return attach_scrwin;
7184 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7185 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7187 static GtkWidget *compose_create_others(Compose *compose)
7190 GtkWidget *savemsg_checkbtn;
7191 GtkWidget *savemsg_combo;
7192 GtkWidget *savemsg_select;
7195 gchar *folderidentifier;
7197 /* Table for settings */
7198 table = gtk_table_new(3, 1, FALSE);
7199 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7200 gtk_widget_show(table);
7201 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7204 /* Save Message to folder */
7205 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7206 gtk_widget_show(savemsg_checkbtn);
7207 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7208 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7209 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7211 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7212 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7214 #if !GTK_CHECK_VERSION(2, 24, 0)
7215 savemsg_combo = gtk_combo_box_entry_new_text();
7217 savemsg_combo = gtk_combo_box_text_new_with_entry();
7219 compose->savemsg_checkbtn = savemsg_checkbtn;
7220 compose->savemsg_combo = savemsg_combo;
7221 gtk_widget_show(savemsg_combo);
7223 if (prefs_common.compose_save_to_history)
7224 #if !GTK_CHECK_VERSION(2, 24, 0)
7225 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7226 prefs_common.compose_save_to_history);
7228 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7229 prefs_common.compose_save_to_history);
7231 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7232 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7233 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7234 G_CALLBACK(compose_grab_focus_cb), compose);
7235 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7236 folderidentifier = folder_item_get_identifier(account_get_special_folder
7237 (compose->account, F_OUTBOX));
7238 compose_set_save_to(compose, folderidentifier);
7239 g_free(folderidentifier);
7242 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7243 gtk_widget_show(savemsg_select);
7244 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7245 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7246 G_CALLBACK(compose_savemsg_select_cb),
7252 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7254 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7255 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7258 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7263 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7266 path = folder_item_get_identifier(dest);
7268 compose_set_save_to(compose, path);
7272 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7273 GdkAtom clip, GtkTextIter *insert_place);
7276 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7280 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7282 if (event->button == 3) {
7284 GtkTextIter sel_start, sel_end;
7285 gboolean stuff_selected;
7287 /* move the cursor to allow GtkAspell to check the word
7288 * under the mouse */
7289 if (event->x && event->y) {
7290 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7291 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7293 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7296 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7297 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7300 stuff_selected = gtk_text_buffer_get_selection_bounds(
7302 &sel_start, &sel_end);
7304 gtk_text_buffer_place_cursor (buffer, &iter);
7305 /* reselect stuff */
7307 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7308 gtk_text_buffer_select_range(buffer,
7309 &sel_start, &sel_end);
7311 return FALSE; /* pass the event so that the right-click goes through */
7314 if (event->button == 2) {
7319 /* get the middle-click position to paste at the correct place */
7320 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7321 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7323 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7326 entry_paste_clipboard(compose, text,
7327 prefs_common.linewrap_pastes,
7328 GDK_SELECTION_PRIMARY, &iter);
7336 static void compose_spell_menu_changed(void *data)
7338 Compose *compose = (Compose *)data;
7340 GtkWidget *menuitem;
7341 GtkWidget *parent_item;
7342 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7345 if (compose->gtkaspell == NULL)
7348 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7349 "/Menu/Spelling/Options");
7351 /* setting the submenu removes /Spelling/Options from the factory
7352 * so we need to save it */
7354 if (parent_item == NULL) {
7355 parent_item = compose->aspell_options_menu;
7356 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7358 compose->aspell_options_menu = parent_item;
7360 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7362 spell_menu = g_slist_reverse(spell_menu);
7363 for (items = spell_menu;
7364 items; items = items->next) {
7365 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7366 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7367 gtk_widget_show(GTK_WIDGET(menuitem));
7369 g_slist_free(spell_menu);
7371 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7372 gtk_widget_show(parent_item);
7375 static void compose_dict_changed(void *data)
7377 Compose *compose = (Compose *) data;
7379 if(!compose->gtkaspell)
7381 if(compose->gtkaspell->recheck_when_changing_dict == FALSE)
7384 gtkaspell_highlight_all(compose->gtkaspell);
7385 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7389 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7391 Compose *compose = (Compose *)data;
7392 GdkEventButton event;
7395 event.time = gtk_get_current_event_time();
7399 return text_clicked(compose->text, &event, compose);
7402 static gboolean compose_force_window_origin = TRUE;
7403 static Compose *compose_create(PrefsAccount *account,
7412 GtkWidget *handlebox;
7414 GtkWidget *notebook;
7416 GtkWidget *attach_hbox;
7417 GtkWidget *attach_lab1;
7418 GtkWidget *attach_lab2;
7423 GtkWidget *subject_hbox;
7424 GtkWidget *subject_frame;
7425 GtkWidget *subject_entry;
7429 GtkWidget *edit_vbox;
7430 GtkWidget *ruler_hbox;
7432 GtkWidget *scrolledwin;
7434 GtkTextBuffer *buffer;
7435 GtkClipboard *clipboard;
7437 UndoMain *undostruct;
7439 GtkWidget *popupmenu;
7440 GtkWidget *tmpl_menu;
7441 GtkActionGroup *action_group = NULL;
7444 GtkAspell * gtkaspell = NULL;
7447 static GdkGeometry geometry;
7449 cm_return_val_if_fail(account != NULL, NULL);
7451 debug_print("Creating compose window...\n");
7452 compose = g_new0(Compose, 1);
7454 compose->batch = batch;
7455 compose->account = account;
7456 compose->folder = folder;
7458 compose->mutex = cm_mutex_new();
7459 compose->set_cursor_pos = -1;
7461 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7463 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7464 gtk_widget_set_size_request(window, prefs_common.compose_width,
7465 prefs_common.compose_height);
7467 if (!geometry.max_width) {
7468 geometry.max_width = gdk_screen_width();
7469 geometry.max_height = gdk_screen_height();
7472 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7473 &geometry, GDK_HINT_MAX_SIZE);
7474 if (!geometry.min_width) {
7475 geometry.min_width = 600;
7476 geometry.min_height = 440;
7478 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7479 &geometry, GDK_HINT_MIN_SIZE);
7481 #ifndef GENERIC_UMPC
7482 if (compose_force_window_origin)
7483 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7484 prefs_common.compose_y);
7486 g_signal_connect(G_OBJECT(window), "delete_event",
7487 G_CALLBACK(compose_delete_cb), compose);
7488 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7489 gtk_widget_realize(window);
7491 gtkut_widget_set_composer_icon(window);
7493 vbox = gtk_vbox_new(FALSE, 0);
7494 gtk_container_add(GTK_CONTAINER(window), vbox);
7496 compose->ui_manager = gtk_ui_manager_new();
7497 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7498 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7499 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7500 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7501 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7502 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7503 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7504 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7505 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7506 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7540 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7554 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7558 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7560 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7562 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7566 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7568 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7569 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7572 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7582 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7583 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7589 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7596 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7597 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7599 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7602 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7603 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7604 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7605 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7606 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7607 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7608 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7610 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7611 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7612 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7613 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7614 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7616 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7618 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7619 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7620 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7621 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7622 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7624 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7625 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)
7626 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)
7627 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7629 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7631 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7632 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)
7633 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)
7635 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7637 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7638 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)
7639 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7641 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7642 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)
7643 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7645 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7647 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7648 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)
7649 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7650 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7651 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7653 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7654 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)
7655 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)
7656 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7657 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7659 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7660 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7661 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7662 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7663 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7664 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7666 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7667 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7668 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)
7670 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7671 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7672 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7676 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7677 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7678 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7679 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7680 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7681 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7684 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7686 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7687 gtk_widget_show_all(menubar);
7689 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7690 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7692 if (prefs_common.toolbar_detachable) {
7693 handlebox = gtk_handle_box_new();
7695 handlebox = gtk_hbox_new(FALSE, 0);
7697 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7699 gtk_widget_realize(handlebox);
7700 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7703 vbox2 = gtk_vbox_new(FALSE, 2);
7704 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7705 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7708 notebook = gtk_notebook_new();
7709 gtk_widget_show(notebook);
7711 /* header labels and entries */
7712 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7713 compose_create_header(compose),
7714 gtk_label_new_with_mnemonic(_("Hea_der")));
7715 /* attachment list */
7716 attach_hbox = gtk_hbox_new(FALSE, 0);
7717 gtk_widget_show(attach_hbox);
7719 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7720 gtk_widget_show(attach_lab1);
7721 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7723 attach_lab2 = gtk_label_new("");
7724 gtk_widget_show(attach_lab2);
7725 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7727 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7728 compose_create_attach(compose),
7731 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7732 compose_create_others(compose),
7733 gtk_label_new_with_mnemonic(_("Othe_rs")));
7736 subject_hbox = gtk_hbox_new(FALSE, 0);
7737 gtk_widget_show(subject_hbox);
7739 subject_frame = gtk_frame_new(NULL);
7740 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7741 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7742 gtk_widget_show(subject_frame);
7744 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7745 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7746 gtk_widget_show(subject);
7748 label = gtk_label_new_with_mnemonic(_("_Subject:"));
7749 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7750 gtk_widget_show(label);
7753 subject_entry = claws_spell_entry_new();
7755 subject_entry = gtk_entry_new();
7757 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7758 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7759 G_CALLBACK(compose_grab_focus_cb), compose);
7760 gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
7761 gtk_widget_show(subject_entry);
7762 compose->subject_entry = subject_entry;
7763 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7765 edit_vbox = gtk_vbox_new(FALSE, 0);
7767 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7770 ruler_hbox = gtk_hbox_new(FALSE, 0);
7771 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7773 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7774 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7775 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7779 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7780 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7781 GTK_POLICY_AUTOMATIC,
7782 GTK_POLICY_AUTOMATIC);
7783 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7785 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7787 text = gtk_text_view_new();
7788 if (prefs_common.show_compose_margin) {
7789 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7790 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7792 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7793 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7794 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7795 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7796 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7798 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7799 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7800 G_CALLBACK(compose_edit_size_alloc),
7802 g_signal_connect(G_OBJECT(buffer), "changed",
7803 G_CALLBACK(compose_changed_cb), compose);
7804 g_signal_connect(G_OBJECT(text), "grab_focus",
7805 G_CALLBACK(compose_grab_focus_cb), compose);
7806 g_signal_connect(G_OBJECT(buffer), "insert_text",
7807 G_CALLBACK(text_inserted), compose);
7808 g_signal_connect(G_OBJECT(text), "button_press_event",
7809 G_CALLBACK(text_clicked), compose);
7810 g_signal_connect(G_OBJECT(text), "popup-menu",
7811 G_CALLBACK(compose_popup_menu), compose);
7812 g_signal_connect(G_OBJECT(subject_entry), "changed",
7813 G_CALLBACK(compose_changed_cb), compose);
7814 g_signal_connect(G_OBJECT(subject_entry), "activate",
7815 G_CALLBACK(compose_subject_entry_activated), compose);
7818 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7819 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7820 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7821 g_signal_connect(G_OBJECT(text), "drag_data_received",
7822 G_CALLBACK(compose_insert_drag_received_cb),
7824 g_signal_connect(G_OBJECT(text), "drag-drop",
7825 G_CALLBACK(compose_drag_drop),
7827 g_signal_connect(G_OBJECT(text), "key-press-event",
7828 G_CALLBACK(completion_set_focus_to_subject),
7830 gtk_widget_show_all(vbox);
7832 /* pane between attach clist and text */
7833 paned = gtk_vpaned_new();
7834 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7835 gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
7836 gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
7837 gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
7838 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7839 G_CALLBACK(compose_notebook_size_alloc), paned);
7841 gtk_widget_show_all(paned);
7844 if (prefs_common.textfont) {
7845 PangoFontDescription *font_desc;
7847 font_desc = pango_font_description_from_string
7848 (prefs_common.textfont);
7850 gtk_widget_modify_font(text, font_desc);
7851 pango_font_description_free(font_desc);
7855 gtk_action_group_add_actions(action_group, compose_popup_entries,
7856 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7857 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7858 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7859 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7860 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7861 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7862 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7864 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7866 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7867 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7868 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7870 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7872 undostruct = undo_init(text);
7873 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7876 address_completion_start(window);
7878 compose->window = window;
7879 compose->vbox = vbox;
7880 compose->menubar = menubar;
7881 compose->handlebox = handlebox;
7883 compose->vbox2 = vbox2;
7885 compose->paned = paned;
7887 compose->attach_label = attach_lab2;
7889 compose->notebook = notebook;
7890 compose->edit_vbox = edit_vbox;
7891 compose->ruler_hbox = ruler_hbox;
7892 compose->ruler = ruler;
7893 compose->scrolledwin = scrolledwin;
7894 compose->text = text;
7896 compose->focused_editable = NULL;
7898 compose->popupmenu = popupmenu;
7900 compose->tmpl_menu = tmpl_menu;
7902 compose->mode = mode;
7903 compose->rmode = mode;
7905 compose->targetinfo = NULL;
7906 compose->replyinfo = NULL;
7907 compose->fwdinfo = NULL;
7909 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7910 g_str_equal, (GDestroyNotify) g_free, NULL);
7912 compose->replyto = NULL;
7914 compose->bcc = NULL;
7915 compose->followup_to = NULL;
7917 compose->ml_post = NULL;
7919 compose->inreplyto = NULL;
7920 compose->references = NULL;
7921 compose->msgid = NULL;
7922 compose->boundary = NULL;
7924 compose->autowrap = prefs_common.autowrap;
7925 compose->autoindent = prefs_common.auto_indent;
7926 compose->use_signing = FALSE;
7927 compose->use_encryption = FALSE;
7928 compose->privacy_system = NULL;
7930 compose->modified = FALSE;
7932 compose->return_receipt = FALSE;
7934 compose->to_list = NULL;
7935 compose->newsgroup_list = NULL;
7937 compose->undostruct = undostruct;
7939 compose->sig_str = NULL;
7941 compose->exteditor_file = NULL;
7942 compose->exteditor_pid = -1;
7943 compose->exteditor_tag = -1;
7944 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
7946 compose->folder_update_callback_id =
7947 hooks_register_hook(FOLDER_UPDATE_HOOKLIST,
7948 compose_update_folder_hook,
7949 (gpointer) compose);
7952 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7953 if (mode != COMPOSE_REDIRECT) {
7954 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7955 strcmp(prefs_common.dictionary, "")) {
7956 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7957 prefs_common.alt_dictionary,
7958 conv_get_locale_charset_str(),
7959 prefs_common.misspelled_col,
7960 prefs_common.check_while_typing,
7961 prefs_common.recheck_when_changing_dict,
7962 prefs_common.use_alternate,
7963 prefs_common.use_both_dicts,
7964 GTK_TEXT_VIEW(text),
7965 GTK_WINDOW(compose->window),
7966 compose_dict_changed,
7967 compose_spell_menu_changed,
7970 alertpanel_error(_("Spell checker could not "
7972 gtkaspell_checkers_strerror());
7973 gtkaspell_checkers_reset_error();
7975 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7979 compose->gtkaspell = gtkaspell;
7980 compose_spell_menu_changed(compose);
7981 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7984 compose_select_account(compose, account, TRUE);
7986 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7987 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7989 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7990 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7992 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7993 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7995 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7996 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7998 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7999 if (account->protocol != A_NNTP)
8000 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8001 prefs_common_translated_header_name("To:"));
8003 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
8004 prefs_common_translated_header_name("Newsgroups:"));
8006 #ifndef USE_NEW_ADDRBOOK
8007 addressbook_set_target_compose(compose);
8009 if (mode != COMPOSE_REDIRECT)
8010 compose_set_template_menu(compose);
8012 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
8015 compose_list = g_list_append(compose_list, compose);
8017 if (!prefs_common.show_ruler)
8018 gtk_widget_hide(ruler_hbox);
8020 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
8023 compose->priority = PRIORITY_NORMAL;
8024 compose_update_priority_menu_item(compose);
8026 compose_set_out_encoding(compose);
8029 compose_update_actions_menu(compose);
8031 /* Privacy Systems menu */
8032 compose_update_privacy_systems_menu(compose);
8034 activate_privacy_system(compose, account, TRUE);
8035 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
8037 gtk_widget_realize(window);
8039 gtk_widget_show(window);
8045 static GtkWidget *compose_account_option_menu_create(Compose *compose)
8050 GtkWidget *optmenubox;
8053 GtkWidget *from_name = NULL;
8055 gint num = 0, def_menu = 0;
8057 accounts = account_get_list();
8058 cm_return_val_if_fail(accounts != NULL, NULL);
8060 optmenubox = gtk_event_box_new();
8061 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
8062 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8064 hbox = gtk_hbox_new(FALSE, 6);
8065 from_name = gtk_entry_new();
8067 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
8068 G_CALLBACK(compose_grab_focus_cb), compose);
8070 for (; accounts != NULL; accounts = accounts->next, num++) {
8071 PrefsAccount *ac = (PrefsAccount *)accounts->data;
8072 gchar *name, *from = NULL;
8074 if (ac == compose->account) def_menu = num;
8076 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
8079 if (ac == compose->account) {
8080 if (ac->name && *ac->name) {
8082 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
8083 from = g_strdup_printf("%s <%s>",
8085 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8087 from = g_strdup_printf("%s",
8089 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8092 COMBOBOX_ADD(menu, name, ac->account_id);
8097 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8099 g_signal_connect(G_OBJECT(optmenu), "changed",
8100 G_CALLBACK(account_activated),
8102 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8103 G_CALLBACK(compose_entry_popup_extend),
8106 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8107 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8109 /* Putting only the GtkEntry into focus chain of parent hbox causes
8110 * the account selector combobox next to it to be unreachable when
8111 * navigating widgets in GtkTable with up/down arrow keys.
8112 * Note: gtk_widget_set_can_focus() was not enough. */
8114 l = g_list_prepend(l, from_name);
8115 gtk_container_set_focus_chain(GTK_CONTAINER(hbox), l);
8118 CLAWS_SET_TIP(optmenubox,
8119 _("Account to use for this email"));
8120 CLAWS_SET_TIP(from_name,
8121 _("Sender address to be used"));
8123 compose->account_combo = optmenu;
8124 compose->from_name = from_name;
8129 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8131 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8132 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8133 Compose *compose = (Compose *) data;
8135 compose->priority = value;
8139 static void compose_reply_change_mode(Compose *compose,
8142 gboolean was_modified = compose->modified;
8144 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8146 cm_return_if_fail(compose->replyinfo != NULL);
8148 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8150 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8152 if (action == COMPOSE_REPLY_TO_ALL)
8154 if (action == COMPOSE_REPLY_TO_SENDER)
8156 if (action == COMPOSE_REPLY_TO_LIST)
8159 compose_remove_header_entries(compose);
8160 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8161 if (compose->account->set_autocc && compose->account->auto_cc)
8162 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8164 if (compose->account->set_autobcc && compose->account->auto_bcc)
8165 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8167 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8168 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8169 compose_show_first_last_header(compose, TRUE);
8170 compose->modified = was_modified;
8171 compose_set_title(compose);
8174 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8176 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8177 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8178 Compose *compose = (Compose *) data;
8181 compose_reply_change_mode(compose, value);
8184 static void compose_update_priority_menu_item(Compose * compose)
8186 GtkWidget *menuitem = NULL;
8187 switch (compose->priority) {
8188 case PRIORITY_HIGHEST:
8189 menuitem = gtk_ui_manager_get_widget
8190 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8193 menuitem = gtk_ui_manager_get_widget
8194 (compose->ui_manager, "/Menu/Options/Priority/High");
8196 case PRIORITY_NORMAL:
8197 menuitem = gtk_ui_manager_get_widget
8198 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8201 menuitem = gtk_ui_manager_get_widget
8202 (compose->ui_manager, "/Menu/Options/Priority/Low");
8204 case PRIORITY_LOWEST:
8205 menuitem = gtk_ui_manager_get_widget
8206 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8209 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8212 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8214 Compose *compose = (Compose *) data;
8216 gboolean can_sign = FALSE, can_encrypt = FALSE;
8218 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8220 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8223 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8224 g_free(compose->privacy_system);
8225 compose->privacy_system = NULL;
8226 if (systemid != NULL) {
8227 compose->privacy_system = g_strdup(systemid);
8229 can_sign = privacy_system_can_sign(systemid);
8230 can_encrypt = privacy_system_can_encrypt(systemid);
8233 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8235 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8236 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8239 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8241 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8242 GtkWidget *menuitem = NULL;
8243 GList *children, *amenu;
8244 gboolean can_sign = FALSE, can_encrypt = FALSE;
8245 gboolean found = FALSE;
8247 if (compose->privacy_system != NULL) {
8249 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8250 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8251 cm_return_if_fail(menuitem != NULL);
8253 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8256 while (amenu != NULL) {
8257 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8258 if (systemid != NULL) {
8259 if (strcmp(systemid, compose->privacy_system) == 0 &&
8260 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8261 menuitem = GTK_WIDGET(amenu->data);
8263 can_sign = privacy_system_can_sign(systemid);
8264 can_encrypt = privacy_system_can_encrypt(systemid);
8268 } else if (strlen(compose->privacy_system) == 0 &&
8269 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8270 menuitem = GTK_WIDGET(amenu->data);
8273 can_encrypt = FALSE;
8278 amenu = amenu->next;
8280 g_list_free(children);
8281 if (menuitem != NULL)
8282 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8284 if (warn && !found && strlen(compose->privacy_system)) {
8285 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8286 "will not be able to sign or encrypt this message."),
8287 compose->privacy_system);
8291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8295 static void compose_set_out_encoding(Compose *compose)
8297 CharSet out_encoding;
8298 const gchar *branch = NULL;
8299 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8301 switch(out_encoding) {
8302 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8303 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8304 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8305 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8306 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8307 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8308 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8309 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8310 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8311 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8312 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8313 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8314 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8315 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8316 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8317 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8318 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8319 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8320 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8321 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8322 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8323 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8324 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8325 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8326 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8327 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8328 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8329 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8330 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8331 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8332 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8333 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8334 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8336 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8339 static void compose_set_template_menu(Compose *compose)
8341 GSList *tmpl_list, *cur;
8345 tmpl_list = template_get_config();
8347 menu = gtk_menu_new();
8349 gtk_menu_set_accel_group (GTK_MENU (menu),
8350 gtk_ui_manager_get_accel_group(compose->ui_manager));
8351 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8352 Template *tmpl = (Template *)cur->data;
8353 gchar *accel_path = NULL;
8354 item = gtk_menu_item_new_with_label(tmpl->name);
8355 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8356 g_signal_connect(G_OBJECT(item), "activate",
8357 G_CALLBACK(compose_template_activate_cb),
8359 g_object_set_data(G_OBJECT(item), "template", tmpl);
8360 gtk_widget_show(item);
8361 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8362 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8366 gtk_widget_show(menu);
8367 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8370 void compose_update_actions_menu(Compose *compose)
8372 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8375 static void compose_update_privacy_systems_menu(Compose *compose)
8377 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8378 GSList *systems, *cur;
8380 GtkWidget *system_none;
8382 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8383 GtkWidget *privacy_menu = gtk_menu_new();
8385 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8386 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8388 g_signal_connect(G_OBJECT(system_none), "activate",
8389 G_CALLBACK(compose_set_privacy_system_cb), compose);
8391 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8392 gtk_widget_show(system_none);
8394 systems = privacy_get_system_ids();
8395 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8396 gchar *systemid = cur->data;
8398 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8399 widget = gtk_radio_menu_item_new_with_label(group,
8400 privacy_system_get_name(systemid));
8401 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8402 g_strdup(systemid), g_free);
8403 g_signal_connect(G_OBJECT(widget), "activate",
8404 G_CALLBACK(compose_set_privacy_system_cb), compose);
8406 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8407 gtk_widget_show(widget);
8410 g_slist_free(systems);
8411 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8412 gtk_widget_show_all(privacy_menu);
8413 gtk_widget_show_all(privacy_menuitem);
8416 void compose_reflect_prefs_all(void)
8421 for (cur = compose_list; cur != NULL; cur = cur->next) {
8422 compose = (Compose *)cur->data;
8423 compose_set_template_menu(compose);
8427 void compose_reflect_prefs_pixmap_theme(void)
8432 for (cur = compose_list; cur != NULL; cur = cur->next) {
8433 compose = (Compose *)cur->data;
8434 toolbar_update(TOOLBAR_COMPOSE, compose);
8438 static const gchar *compose_quote_char_from_context(Compose *compose)
8440 const gchar *qmark = NULL;
8442 cm_return_val_if_fail(compose != NULL, NULL);
8444 switch (compose->mode) {
8445 /* use forward-specific quote char */
8446 case COMPOSE_FORWARD:
8447 case COMPOSE_FORWARD_AS_ATTACH:
8448 case COMPOSE_FORWARD_INLINE:
8449 if (compose->folder && compose->folder->prefs &&
8450 compose->folder->prefs->forward_with_format)
8451 qmark = compose->folder->prefs->forward_quotemark;
8452 else if (compose->account->forward_with_format)
8453 qmark = compose->account->forward_quotemark;
8455 qmark = prefs_common.fw_quotemark;
8458 /* use reply-specific quote char in all other modes */
8460 if (compose->folder && compose->folder->prefs &&
8461 compose->folder->prefs->reply_with_format)
8462 qmark = compose->folder->prefs->reply_quotemark;
8463 else if (compose->account->reply_with_format)
8464 qmark = compose->account->reply_quotemark;
8466 qmark = prefs_common.quotemark;
8470 if (qmark == NULL || *qmark == '\0')
8476 static void compose_template_apply(Compose *compose, Template *tmpl,
8480 GtkTextBuffer *buffer;
8484 gchar *parsed_str = NULL;
8485 gint cursor_pos = 0;
8486 const gchar *err_msg = _("The body of the template has an error at line %d.");
8489 /* process the body */
8491 text = GTK_TEXT_VIEW(compose->text);
8492 buffer = gtk_text_view_get_buffer(text);
8495 qmark = compose_quote_char_from_context(compose);
8497 if (compose->replyinfo != NULL) {
8500 gtk_text_buffer_set_text(buffer, "", -1);
8501 mark = gtk_text_buffer_get_insert(buffer);
8502 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8504 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8505 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8507 } else if (compose->fwdinfo != NULL) {
8510 gtk_text_buffer_set_text(buffer, "", -1);
8511 mark = gtk_text_buffer_get_insert(buffer);
8512 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8514 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8515 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8518 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8520 GtkTextIter start, end;
8523 gtk_text_buffer_get_start_iter(buffer, &start);
8524 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8525 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8527 /* clear the buffer now */
8529 gtk_text_buffer_set_text(buffer, "", -1);
8531 parsed_str = compose_quote_fmt(compose, dummyinfo,
8532 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8533 procmsg_msginfo_free( dummyinfo );
8539 gtk_text_buffer_set_text(buffer, "", -1);
8540 mark = gtk_text_buffer_get_insert(buffer);
8541 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8544 if (replace && parsed_str && compose->account->auto_sig)
8545 compose_insert_sig(compose, FALSE);
8547 if (replace && parsed_str) {
8548 gtk_text_buffer_get_start_iter(buffer, &iter);
8549 gtk_text_buffer_place_cursor(buffer, &iter);
8553 cursor_pos = quote_fmt_get_cursor_pos();
8554 compose->set_cursor_pos = cursor_pos;
8555 if (cursor_pos == -1)
8557 gtk_text_buffer_get_start_iter(buffer, &iter);
8558 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8559 gtk_text_buffer_place_cursor(buffer, &iter);
8562 /* process the other fields */
8564 compose_template_apply_fields(compose, tmpl);
8565 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8566 quote_fmt_reset_vartable();
8567 compose_changed_cb(NULL, compose);
8570 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8571 gtkaspell_highlight_all(compose->gtkaspell);
8575 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8577 MsgInfo* dummyinfo = NULL;
8578 MsgInfo *msginfo = NULL;
8581 if (compose->replyinfo != NULL)
8582 msginfo = compose->replyinfo;
8583 else if (compose->fwdinfo != NULL)
8584 msginfo = compose->fwdinfo;
8586 dummyinfo = compose_msginfo_new_from_compose(compose);
8587 msginfo = dummyinfo;
8590 if (tmpl->from && *tmpl->from != '\0') {
8592 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8593 compose->gtkaspell);
8595 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8597 quote_fmt_scan_string(tmpl->from);
8600 buf = quote_fmt_get_buffer();
8602 alertpanel_error(_("Template From format error."));
8604 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8608 if (tmpl->to && *tmpl->to != '\0') {
8610 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8611 compose->gtkaspell);
8613 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8615 quote_fmt_scan_string(tmpl->to);
8618 buf = quote_fmt_get_buffer();
8620 alertpanel_error(_("Template To format error."));
8622 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8626 if (tmpl->cc && *tmpl->cc != '\0') {
8628 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8629 compose->gtkaspell);
8631 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8633 quote_fmt_scan_string(tmpl->cc);
8636 buf = quote_fmt_get_buffer();
8638 alertpanel_error(_("Template Cc format error."));
8640 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8644 if (tmpl->bcc && *tmpl->bcc != '\0') {
8646 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8647 compose->gtkaspell);
8649 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8651 quote_fmt_scan_string(tmpl->bcc);
8654 buf = quote_fmt_get_buffer();
8656 alertpanel_error(_("Template Bcc format error."));
8658 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8662 if (tmpl->replyto && *tmpl->replyto != '\0') {
8664 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8665 compose->gtkaspell);
8667 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8669 quote_fmt_scan_string(tmpl->replyto);
8672 buf = quote_fmt_get_buffer();
8674 alertpanel_error(_("Template Reply-To format error."));
8676 compose_entry_append(compose, buf, COMPOSE_REPLYTO, PREF_TEMPLATE);
8680 /* process the subject */
8681 if (tmpl->subject && *tmpl->subject != '\0') {
8683 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8684 compose->gtkaspell);
8686 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8688 quote_fmt_scan_string(tmpl->subject);
8691 buf = quote_fmt_get_buffer();
8693 alertpanel_error(_("Template subject format error."));
8695 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8699 procmsg_msginfo_free( dummyinfo );
8702 static void compose_destroy(Compose *compose)
8704 GtkAllocation allocation;
8705 GtkTextBuffer *buffer;
8706 GtkClipboard *clipboard;
8708 compose_list = g_list_remove(compose_list, compose);
8710 if (compose->updating) {
8711 debug_print("danger, not destroying anything now\n");
8712 compose->deferred_destroy = TRUE;
8716 /* NOTE: address_completion_end() does nothing with the window
8717 * however this may change. */
8718 address_completion_end(compose->window);
8720 slist_free_strings_full(compose->to_list);
8721 slist_free_strings_full(compose->newsgroup_list);
8722 slist_free_strings_full(compose->header_list);
8724 slist_free_strings_full(extra_headers);
8725 extra_headers = NULL;
8727 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8729 g_hash_table_destroy(compose->email_hashtable);
8731 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST,
8732 compose->folder_update_callback_id);
8734 procmsg_msginfo_free(compose->targetinfo);
8735 procmsg_msginfo_free(compose->replyinfo);
8736 procmsg_msginfo_free(compose->fwdinfo);
8738 g_free(compose->replyto);
8739 g_free(compose->cc);
8740 g_free(compose->bcc);
8741 g_free(compose->newsgroups);
8742 g_free(compose->followup_to);
8744 g_free(compose->ml_post);
8746 g_free(compose->inreplyto);
8747 g_free(compose->references);
8748 g_free(compose->msgid);
8749 g_free(compose->boundary);
8751 g_free(compose->redirect_filename);
8752 if (compose->undostruct)
8753 undo_destroy(compose->undostruct);
8755 g_free(compose->sig_str);
8757 g_free(compose->exteditor_file);
8759 g_free(compose->orig_charset);
8761 g_free(compose->privacy_system);
8763 #ifndef USE_NEW_ADDRBOOK
8764 if (addressbook_get_target_compose() == compose)
8765 addressbook_set_target_compose(NULL);
8768 if (compose->gtkaspell) {
8769 gtkaspell_delete(compose->gtkaspell);
8770 compose->gtkaspell = NULL;
8774 if (!compose->batch) {
8775 gtk_widget_get_allocation(compose->window, &allocation);
8776 prefs_common.compose_width = allocation.width;
8777 prefs_common.compose_height = allocation.height;
8780 if (!gtk_widget_get_parent(compose->paned))
8781 gtk_widget_destroy(compose->paned);
8782 gtk_widget_destroy(compose->popupmenu);
8784 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8785 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8786 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8788 gtk_widget_destroy(compose->window);
8789 toolbar_destroy(compose->toolbar);
8790 g_free(compose->toolbar);
8791 cm_mutex_free(compose->mutex);
8795 static void compose_attach_info_free(AttachInfo *ainfo)
8797 g_free(ainfo->file);
8798 g_free(ainfo->content_type);
8799 g_free(ainfo->name);
8800 g_free(ainfo->charset);
8804 static void compose_attach_update_label(Compose *compose)
8809 GtkTreeModel *model;
8814 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8815 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8816 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8820 while(gtk_tree_model_iter_next(model, &iter))
8823 text = g_strdup_printf("(%d)", i);
8824 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8828 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8830 Compose *compose = (Compose *)data;
8831 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8832 GtkTreeSelection *selection;
8834 GtkTreeModel *model;
8836 selection = gtk_tree_view_get_selection(tree_view);
8837 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8842 for (cur = sel; cur != NULL; cur = cur->next) {
8843 GtkTreePath *path = cur->data;
8844 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8847 gtk_tree_path_free(path);
8850 for (cur = sel; cur != NULL; cur = cur->next) {
8851 GtkTreeRowReference *ref = cur->data;
8852 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8855 if (gtk_tree_model_get_iter(model, &iter, path))
8856 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8858 gtk_tree_path_free(path);
8859 gtk_tree_row_reference_free(ref);
8863 compose_attach_update_label(compose);
8866 static struct _AttachProperty
8869 GtkWidget *mimetype_entry;
8870 GtkWidget *encoding_optmenu;
8871 GtkWidget *path_entry;
8872 GtkWidget *filename_entry;
8874 GtkWidget *cancel_btn;
8877 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8879 gtk_tree_path_free((GtkTreePath *)ptr);
8882 static void compose_attach_property(GtkAction *action, gpointer data)
8884 Compose *compose = (Compose *)data;
8885 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8887 GtkComboBox *optmenu;
8888 GtkTreeSelection *selection;
8890 GtkTreeModel *model;
8893 static gboolean cancelled;
8895 /* only if one selected */
8896 selection = gtk_tree_view_get_selection(tree_view);
8897 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8900 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8904 path = (GtkTreePath *) sel->data;
8905 gtk_tree_model_get_iter(model, &iter, path);
8906 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8909 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8915 if (!attach_prop.window)
8916 compose_attach_property_create(&cancelled);
8917 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8918 gtk_widget_grab_focus(attach_prop.ok_btn);
8919 gtk_widget_show(attach_prop.window);
8920 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8921 GTK_WINDOW(compose->window));
8923 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8924 if (ainfo->encoding == ENC_UNKNOWN)
8925 combobox_select_by_data(optmenu, ENC_BASE64);
8927 combobox_select_by_data(optmenu, ainfo->encoding);
8929 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8930 ainfo->content_type ? ainfo->content_type : "");
8931 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8932 ainfo->file ? ainfo->file : "");
8933 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8934 ainfo->name ? ainfo->name : "");
8937 const gchar *entry_text;
8939 gchar *cnttype = NULL;
8946 gtk_widget_hide(attach_prop.window);
8947 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8952 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8953 if (*entry_text != '\0') {
8956 text = g_strstrip(g_strdup(entry_text));
8957 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8958 cnttype = g_strdup(text);
8961 alertpanel_error(_("Invalid MIME type."));
8967 ainfo->encoding = combobox_get_active_data(optmenu);
8969 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8970 if (*entry_text != '\0') {
8971 if (is_file_exist(entry_text) &&
8972 (size = get_file_size(entry_text)) > 0)
8973 file = g_strdup(entry_text);
8976 (_("File doesn't exist or is empty."));
8982 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8983 if (*entry_text != '\0') {
8984 g_free(ainfo->name);
8985 ainfo->name = g_strdup(entry_text);
8989 g_free(ainfo->content_type);
8990 ainfo->content_type = cnttype;
8993 g_free(ainfo->file);
8997 ainfo->size = (goffset)size;
8999 /* update tree store */
9000 text = to_human_readable(ainfo->size);
9001 gtk_tree_model_get_iter(model, &iter, path);
9002 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
9003 COL_MIMETYPE, ainfo->content_type,
9005 COL_NAME, ainfo->name,
9006 COL_CHARSET, ainfo->charset,
9012 gtk_tree_path_free(path);
9015 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9017 label = gtk_label_new(str); \
9018 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9019 GTK_FILL, 0, 0, 0); \
9020 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9022 entry = gtk_entry_new(); \
9023 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9024 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9027 static void compose_attach_property_create(gboolean *cancelled)
9033 GtkWidget *mimetype_entry;
9036 GtkListStore *optmenu_menu;
9037 GtkWidget *path_entry;
9038 GtkWidget *filename_entry;
9041 GtkWidget *cancel_btn;
9042 GList *mime_type_list, *strlist;
9045 debug_print("Creating attach_property window...\n");
9047 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
9048 gtk_widget_set_size_request(window, 480, -1);
9049 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
9050 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
9051 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
9052 g_signal_connect(G_OBJECT(window), "delete_event",
9053 G_CALLBACK(attach_property_delete_event),
9055 g_signal_connect(G_OBJECT(window), "key_press_event",
9056 G_CALLBACK(attach_property_key_pressed),
9059 vbox = gtk_vbox_new(FALSE, 8);
9060 gtk_container_add(GTK_CONTAINER(window), vbox);
9062 table = gtk_table_new(4, 2, FALSE);
9063 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
9064 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
9065 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
9067 label = gtk_label_new(_("MIME type"));
9068 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
9070 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9071 #if !GTK_CHECK_VERSION(2, 24, 0)
9072 mimetype_entry = gtk_combo_box_entry_new_text();
9074 mimetype_entry = gtk_combo_box_text_new_with_entry();
9076 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
9077 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9079 /* stuff with list */
9080 mime_type_list = procmime_get_mime_type_list();
9082 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
9083 MimeType *type = (MimeType *) mime_type_list->data;
9086 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
9088 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
9091 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
9092 (GCompareFunc)strcmp2);
9095 for (mime_type_list = strlist; mime_type_list != NULL;
9096 mime_type_list = mime_type_list->next) {
9097 #if !GTK_CHECK_VERSION(2, 24, 0)
9098 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
9100 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
9102 g_free(mime_type_list->data);
9104 g_list_free(strlist);
9105 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
9106 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
9108 label = gtk_label_new(_("Encoding"));
9109 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
9111 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
9113 hbox = gtk_hbox_new(FALSE, 0);
9114 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
9115 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
9117 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
9118 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
9120 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9121 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9122 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9123 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9124 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9126 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9128 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9129 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9131 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9132 &ok_btn, GTK_STOCK_OK,
9134 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9135 gtk_widget_grab_default(ok_btn);
9137 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9138 G_CALLBACK(attach_property_ok),
9140 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9141 G_CALLBACK(attach_property_cancel),
9144 gtk_widget_show_all(vbox);
9146 attach_prop.window = window;
9147 attach_prop.mimetype_entry = mimetype_entry;
9148 attach_prop.encoding_optmenu = optmenu;
9149 attach_prop.path_entry = path_entry;
9150 attach_prop.filename_entry = filename_entry;
9151 attach_prop.ok_btn = ok_btn;
9152 attach_prop.cancel_btn = cancel_btn;
9155 #undef SET_LABEL_AND_ENTRY
9157 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9163 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9169 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9170 gboolean *cancelled)
9178 static gboolean attach_property_key_pressed(GtkWidget *widget,
9180 gboolean *cancelled)
9182 if (event && event->keyval == GDK_KEY_Escape) {
9186 if (event && event->keyval == GDK_KEY_Return) {
9194 static void compose_exec_ext_editor(Compose *compose)
9201 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9202 G_DIR_SEPARATOR, compose);
9204 if (pipe(pipe_fds) < 0) {
9210 if ((pid = fork()) < 0) {
9217 /* close the write side of the pipe */
9220 compose->exteditor_file = g_strdup(tmp);
9221 compose->exteditor_pid = pid;
9223 compose_set_ext_editor_sensitive(compose, FALSE);
9226 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9228 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9230 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9234 } else { /* process-monitoring process */
9240 /* close the read side of the pipe */
9243 if (compose_write_body_to_file(compose, tmp) < 0) {
9244 fd_write_all(pipe_fds[1], "2\n", 2);
9248 pid_ed = compose_exec_ext_editor_real(tmp);
9250 fd_write_all(pipe_fds[1], "1\n", 2);
9254 /* wait until editor is terminated */
9255 waitpid(pid_ed, NULL, 0);
9257 fd_write_all(pipe_fds[1], "0\n", 2);
9264 #endif /* G_OS_UNIX */
9268 static gint compose_exec_ext_editor_real(const gchar *file)
9275 cm_return_val_if_fail(file != NULL, -1);
9277 if ((pid = fork()) < 0) {
9282 if (pid != 0) return pid;
9284 /* grandchild process */
9286 if (setpgid(0, getppid()))
9289 if (prefs_common_get_ext_editor_cmd() &&
9290 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9291 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9292 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9294 if (prefs_common_get_ext_editor_cmd())
9295 g_warning("External editor command-line is invalid: '%s'\n",
9296 prefs_common_get_ext_editor_cmd());
9297 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9300 cmdline = strsplit_with_quote(buf, " ", 1024);
9301 execvp(cmdline[0], cmdline);
9304 g_strfreev(cmdline);
9309 static gboolean compose_ext_editor_kill(Compose *compose)
9311 pid_t pgid = compose->exteditor_pid * -1;
9314 ret = kill(pgid, 0);
9316 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9320 msg = g_strdup_printf
9321 (_("The external editor is still working.\n"
9322 "Force terminating the process?\n"
9323 "process group id: %d"), -pgid);
9324 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9325 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9329 if (val == G_ALERTALTERNATE) {
9330 g_source_remove(compose->exteditor_tag);
9331 g_io_channel_shutdown(compose->exteditor_ch,
9333 g_io_channel_unref(compose->exteditor_ch);
9335 if (kill(pgid, SIGTERM) < 0) perror("kill");
9336 waitpid(compose->exteditor_pid, NULL, 0);
9338 g_warning("Terminated process group id: %d", -pgid);
9339 g_warning("Temporary file: %s",
9340 compose->exteditor_file);
9342 compose_set_ext_editor_sensitive(compose, TRUE);
9344 g_free(compose->exteditor_file);
9345 compose->exteditor_file = NULL;
9346 compose->exteditor_pid = -1;
9347 compose->exteditor_ch = NULL;
9348 compose->exteditor_tag = -1;
9356 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9360 Compose *compose = (Compose *)data;
9363 debug_print("Compose: input from monitoring process\n");
9365 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9367 g_io_channel_shutdown(source, FALSE, NULL);
9368 g_io_channel_unref(source);
9370 waitpid(compose->exteditor_pid, NULL, 0);
9372 if (buf[0] == '0') { /* success */
9373 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9374 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9376 gtk_text_buffer_set_text(buffer, "", -1);
9377 compose_insert_file(compose, compose->exteditor_file);
9378 compose_changed_cb(NULL, compose);
9379 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9381 if (claws_unlink(compose->exteditor_file) < 0)
9382 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9383 } else if (buf[0] == '1') { /* failed */
9384 g_warning("Couldn't exec external editor\n");
9385 if (claws_unlink(compose->exteditor_file) < 0)
9386 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9387 } else if (buf[0] == '2') {
9388 g_warning("Couldn't write to file\n");
9389 } else if (buf[0] == '3') {
9390 g_warning("Pipe read failed\n");
9393 compose_set_ext_editor_sensitive(compose, TRUE);
9395 g_free(compose->exteditor_file);
9396 compose->exteditor_file = NULL;
9397 compose->exteditor_pid = -1;
9398 compose->exteditor_ch = NULL;
9399 compose->exteditor_tag = -1;
9404 static void compose_set_ext_editor_sensitive(Compose *compose,
9407 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9408 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9410 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9411 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
9412 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9413 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9414 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9416 gtk_widget_set_sensitive(compose->text, sensitive);
9417 if (compose->toolbar->send_btn)
9418 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9419 if (compose->toolbar->sendl_btn)
9420 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9421 if (compose->toolbar->draft_btn)
9422 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9423 if (compose->toolbar->insert_btn)
9424 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9425 if (compose->toolbar->sig_btn)
9426 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9427 if (compose->toolbar->exteditor_btn)
9428 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9429 if (compose->toolbar->linewrap_current_btn)
9430 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9431 if (compose->toolbar->linewrap_all_btn)
9432 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9434 #endif /* G_OS_UNIX */
9437 * compose_undo_state_changed:
9439 * Change the sensivity of the menuentries undo and redo
9441 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9442 gint redo_state, gpointer data)
9444 Compose *compose = (Compose *)data;
9446 switch (undo_state) {
9447 case UNDO_STATE_TRUE:
9448 if (!undostruct->undo_state) {
9449 undostruct->undo_state = TRUE;
9450 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9453 case UNDO_STATE_FALSE:
9454 if (undostruct->undo_state) {
9455 undostruct->undo_state = FALSE;
9456 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9459 case UNDO_STATE_UNCHANGED:
9461 case UNDO_STATE_REFRESH:
9462 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9465 g_warning("Undo state not recognized");
9469 switch (redo_state) {
9470 case UNDO_STATE_TRUE:
9471 if (!undostruct->redo_state) {
9472 undostruct->redo_state = TRUE;
9473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9476 case UNDO_STATE_FALSE:
9477 if (undostruct->redo_state) {
9478 undostruct->redo_state = FALSE;
9479 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9482 case UNDO_STATE_UNCHANGED:
9484 case UNDO_STATE_REFRESH:
9485 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9488 g_warning("Redo state not recognized");
9493 /* callback functions */
9495 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9496 GtkAllocation *allocation,
9499 prefs_common.compose_notebook_height = gtk_paned_get_position(paned);
9502 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9503 * includes "non-client" (windows-izm) in calculation, so this calculation
9504 * may not be accurate.
9506 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9507 GtkAllocation *allocation,
9508 GtkSHRuler *shruler)
9510 if (prefs_common.show_ruler) {
9511 gint char_width = 0, char_height = 0;
9512 gint line_width_in_chars;
9514 gtkut_get_font_size(GTK_WIDGET(widget),
9515 &char_width, &char_height);
9516 line_width_in_chars =
9517 (allocation->width - allocation->x) / char_width;
9519 /* got the maximum */
9520 gtk_shruler_set_range(GTK_SHRULER(shruler),
9521 0.0, line_width_in_chars, 0);
9530 ComposePrefType type;
9531 gboolean entry_marked;
9534 static void account_activated(GtkComboBox *optmenu, gpointer data)
9536 Compose *compose = (Compose *)data;
9539 gchar *folderidentifier;
9540 gint account_id = 0;
9543 GSList *list, *saved_list = NULL;
9544 HeaderEntryState *state;
9545 GtkRcStyle *style = NULL;
9546 #if !GTK_CHECK_VERSION(3, 0, 0)
9547 static GdkColor yellow;
9548 static gboolean color_set = FALSE;
9550 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9553 /* Get ID of active account in the combo box */
9554 menu = gtk_combo_box_get_model(optmenu);
9555 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu, &iter));
9556 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9558 ac = account_find_from_id(account_id);
9559 cm_return_if_fail(ac != NULL);
9561 if (ac != compose->account) {
9562 compose_select_account(compose, ac, FALSE);
9564 for (list = compose->header_list; list; list = list->next) {
9565 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9567 if (hentry->type == PREF_ACCOUNT || !list->next) {
9568 compose_destroy_headerentry(compose, hentry);
9572 state = g_malloc0(sizeof(HeaderEntryState));
9573 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9574 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9575 state->entry = gtk_editable_get_chars(
9576 GTK_EDITABLE(hentry->entry), 0, -1);
9577 state->type = hentry->type;
9579 #if !GTK_CHECK_VERSION(3, 0, 0)
9581 gdk_color_parse("#f5f6be", &yellow);
9582 color_set = gdk_colormap_alloc_color(
9583 gdk_colormap_get_system(),
9584 &yellow, FALSE, TRUE);
9588 style = gtk_widget_get_modifier_style(hentry->entry);
9589 state->entry_marked = gdk_color_equal(&yellow,
9590 &style->base[GTK_STATE_NORMAL]);
9592 saved_list = g_slist_append(saved_list, state);
9593 compose_destroy_headerentry(compose, hentry);
9596 compose->header_last = NULL;
9597 g_slist_free(compose->header_list);
9598 compose->header_list = NULL;
9599 compose->header_nextrow = 1;
9600 compose_create_header_entry(compose);
9602 if (ac->set_autocc && ac->auto_cc)
9603 compose_entry_append(compose, ac->auto_cc,
9604 COMPOSE_CC, PREF_ACCOUNT);
9606 if (ac->set_autobcc && ac->auto_bcc)
9607 compose_entry_append(compose, ac->auto_bcc,
9608 COMPOSE_BCC, PREF_ACCOUNT);
9610 if (ac->set_autoreplyto && ac->auto_replyto)
9611 compose_entry_append(compose, ac->auto_replyto,
9612 COMPOSE_REPLYTO, PREF_ACCOUNT);
9614 for (list = saved_list; list; list = list->next) {
9615 state = (HeaderEntryState *) list->data;
9617 compose_add_header_entry(compose, state->header,
9618 state->entry, state->type);
9619 if (state->entry_marked)
9620 compose_entry_mark_default_to(compose, state->entry);
9622 g_free(state->header);
9623 g_free(state->entry);
9626 g_slist_free(saved_list);
9628 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9629 (ac->protocol == A_NNTP) ?
9630 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9633 /* Set message save folder */
9634 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9635 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9637 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9638 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9640 compose_set_save_to(compose, NULL);
9641 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9642 folderidentifier = folder_item_get_identifier(account_get_special_folder
9643 (compose->account, F_OUTBOX));
9644 compose_set_save_to(compose, folderidentifier);
9645 g_free(folderidentifier);
9649 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9650 GtkTreeViewColumn *column, Compose *compose)
9652 compose_attach_property(NULL, compose);
9655 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9658 Compose *compose = (Compose *)data;
9659 GtkTreeSelection *attach_selection;
9660 gint attach_nr_selected;
9662 if (!event) return FALSE;
9664 if (event->button == 3) {
9665 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9666 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9668 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9669 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9671 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9672 NULL, NULL, event->button, event->time);
9679 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9682 Compose *compose = (Compose *)data;
9684 if (!event) return FALSE;
9686 switch (event->keyval) {
9687 case GDK_KEY_Delete:
9688 compose_attach_remove_selected(NULL, compose);
9694 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9696 toolbar_comp_set_sensitive(compose, allow);
9697 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9698 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9700 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9702 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9703 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9704 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9706 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9710 static void compose_send_cb(GtkAction *action, gpointer data)
9712 Compose *compose = (Compose *)data;
9714 if (prefs_common.work_offline &&
9715 !inc_offline_should_override(TRUE,
9716 _("Claws Mail needs network access in order "
9717 "to send this email.")))
9720 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9721 g_source_remove(compose->draft_timeout_tag);
9722 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
9725 compose_send(compose);
9728 static void compose_send_later_cb(GtkAction *action, gpointer data)
9730 Compose *compose = (Compose *)data;
9734 compose_allow_user_actions(compose, FALSE);
9735 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9736 compose_allow_user_actions(compose, TRUE);
9740 compose_close(compose);
9741 } else if (val == -1) {
9742 alertpanel_error(_("Could not queue message."));
9743 } else if (val == -2) {
9744 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9745 } else if (val == -3) {
9746 if (privacy_peek_error())
9747 alertpanel_error(_("Could not queue message for sending:\n\n"
9748 "Signature failed: %s"), privacy_get_error());
9749 } else if (val == -4) {
9750 alertpanel_error(_("Could not queue message for sending:\n\n"
9751 "Charset conversion failed."));
9752 } else if (val == -5) {
9753 alertpanel_error(_("Could not queue message for sending:\n\n"
9754 "Couldn't get recipient encryption key."));
9755 } else if (val == -6) {
9758 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9761 #define DRAFTED_AT_EXIT "drafted_at_exit"
9762 static void compose_register_draft(MsgInfo *info)
9764 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9765 DRAFTED_AT_EXIT, NULL);
9766 FILE *fp = g_fopen(filepath, "ab");
9769 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9777 gboolean compose_draft (gpointer data, guint action)
9779 Compose *compose = (Compose *)data;
9784 MsgFlags flag = {0, 0};
9785 static gboolean lock = FALSE;
9786 MsgInfo *newmsginfo;
9788 gboolean target_locked = FALSE;
9789 gboolean err = FALSE;
9791 if (lock) return FALSE;
9793 if (compose->sending)
9796 draft = account_get_special_folder(compose->account, F_DRAFT);
9797 cm_return_val_if_fail(draft != NULL, FALSE);
9799 if (!g_mutex_trylock(compose->mutex)) {
9800 /* we don't want to lock the mutex once it's available,
9801 * because as the only other part of compose.c locking
9802 * it is compose_close - which means once unlocked,
9803 * the compose struct will be freed */
9804 debug_print("couldn't lock mutex, probably sending\n");
9810 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9811 G_DIR_SEPARATOR, compose);
9812 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9813 FILE_OP_ERROR(tmp, "fopen");
9817 /* chmod for security */
9818 if (change_file_mode_rw(fp, tmp) < 0) {
9819 FILE_OP_ERROR(tmp, "chmod");
9820 g_warning("can't change file mode\n");
9823 /* Save draft infos */
9824 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9825 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9827 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9828 gchar *savefolderid;
9830 savefolderid = compose_get_save_to(compose);
9831 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9832 g_free(savefolderid);
9834 if (compose->return_receipt) {
9835 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9837 if (compose->privacy_system) {
9838 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9839 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9840 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9843 /* Message-ID of message replying to */
9844 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9845 gchar *folderid = NULL;
9847 if (compose->replyinfo->folder)
9848 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9849 if (folderid == NULL)
9850 folderid = g_strdup("NULL");
9852 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9855 /* Message-ID of message forwarding to */
9856 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9857 gchar *folderid = NULL;
9859 if (compose->fwdinfo->folder)
9860 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9861 if (folderid == NULL)
9862 folderid = g_strdup("NULL");
9864 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9868 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9869 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9871 sheaders = compose_get_manual_headers_info(compose);
9872 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9875 /* end of headers */
9876 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9883 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9887 if (fclose(fp) == EOF) {
9891 flag.perm_flags = MSG_NEW|MSG_UNREAD;
9892 if (compose->targetinfo) {
9893 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9895 flag.perm_flags |= MSG_LOCKED;
9897 flag.tmp_flags = MSG_DRAFT;
9899 folder_item_scan(draft);
9900 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9901 MsgInfo *tmpinfo = NULL;
9902 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9903 if (compose->msgid) {
9904 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9907 msgnum = tmpinfo->msgnum;
9908 procmsg_msginfo_free(tmpinfo);
9909 debug_print("got draft msgnum %d from scanning\n", msgnum);
9911 debug_print("didn't get draft msgnum after scanning\n");
9914 debug_print("got draft msgnum %d from adding\n", msgnum);
9920 if (action != COMPOSE_AUTO_SAVE) {
9921 if (action != COMPOSE_DRAFT_FOR_EXIT)
9922 alertpanel_error(_("Could not save draft."));
9925 gtkut_window_popup(compose->window);
9926 val = alertpanel_full(_("Could not save draft"),
9927 _("Could not save draft.\n"
9928 "Do you want to cancel exit or discard this email?"),
9929 _("_Cancel exit"), _("_Discard email"), NULL,
9930 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9931 if (val == G_ALERTALTERNATE) {
9933 g_mutex_unlock(compose->mutex); /* must be done before closing */
9934 compose_close(compose);
9938 g_mutex_unlock(compose->mutex); /* must be done before closing */
9947 if (compose->mode == COMPOSE_REEDIT) {
9948 compose_remove_reedit_target(compose, TRUE);
9951 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9954 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9956 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
9958 procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
9959 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9960 procmsg_msginfo_set_flags(newmsginfo, 0,
9961 MSG_HAS_ATTACHMENT);
9963 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9964 compose_register_draft(newmsginfo);
9966 procmsg_msginfo_free(newmsginfo);
9969 folder_item_scan(draft);
9971 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9973 g_mutex_unlock(compose->mutex); /* must be done before closing */
9974 compose_close(compose);
9980 path = folder_item_fetch_msg(draft, msgnum);
9982 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9985 if (g_stat(path, &s) < 0) {
9986 FILE_OP_ERROR(path, "stat");
9992 procmsg_msginfo_free(compose->targetinfo);
9993 compose->targetinfo = procmsg_msginfo_new();
9994 compose->targetinfo->msgnum = msgnum;
9995 compose->targetinfo->size = (goffset)s.st_size;
9996 compose->targetinfo->mtime = s.st_mtime;
9997 compose->targetinfo->folder = draft;
9999 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
10000 compose->mode = COMPOSE_REEDIT;
10002 if (action == COMPOSE_AUTO_SAVE) {
10003 compose->autosaved_draft = compose->targetinfo;
10005 compose->modified = FALSE;
10006 compose_set_title(compose);
10010 g_mutex_unlock(compose->mutex);
10014 void compose_clear_exit_drafts(void)
10016 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10017 DRAFTED_AT_EXIT, NULL);
10018 if (is_file_exist(filepath))
10019 claws_unlink(filepath);
10024 void compose_reopen_exit_drafts(void)
10026 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
10027 DRAFTED_AT_EXIT, NULL);
10028 FILE *fp = g_fopen(filepath, "rb");
10032 while (fgets(buf, sizeof(buf), fp)) {
10033 gchar **parts = g_strsplit(buf, "\t", 2);
10034 const gchar *folder = parts[0];
10035 int msgnum = parts[1] ? atoi(parts[1]):-1;
10037 if (folder && *folder && msgnum > -1) {
10038 FolderItem *item = folder_find_item_from_identifier(folder);
10039 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
10041 compose_reedit(info, FALSE);
10048 compose_clear_exit_drafts();
10051 static void compose_save_cb(GtkAction *action, gpointer data)
10053 Compose *compose = (Compose *)data;
10054 compose_draft(compose, COMPOSE_KEEP_EDITING);
10055 compose->rmode = COMPOSE_REEDIT;
10058 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
10060 if (compose && file_list) {
10063 for ( tmp = file_list; tmp; tmp = tmp->next) {
10064 gchar *file = (gchar *) tmp->data;
10065 gchar *utf8_filename = conv_filename_to_utf8(file);
10066 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
10067 compose_changed_cb(NULL, compose);
10072 g_free(utf8_filename);
10077 static void compose_attach_cb(GtkAction *action, gpointer data)
10079 Compose *compose = (Compose *)data;
10082 if (compose->redirect_filename != NULL)
10085 /* Set focus_window properly, in case we were called via popup menu,
10086 * which unsets it (via focus_out_event callback on compose window). */
10087 manage_window_focus_in(compose->window, NULL, NULL);
10089 file_list = filesel_select_multiple_files_open(_("Select file"));
10092 compose_attach_from_list(compose, file_list, TRUE);
10093 g_list_free(file_list);
10097 static void compose_insert_file_cb(GtkAction *action, gpointer data)
10099 Compose *compose = (Compose *)data;
10101 gint files_inserted = 0;
10103 file_list = filesel_select_multiple_files_open(_("Select file"));
10108 for ( tmp = file_list; tmp; tmp = tmp->next) {
10109 gchar *file = (gchar *) tmp->data;
10110 gchar *filedup = g_strdup(file);
10111 gchar *shortfile = g_path_get_basename(filedup);
10112 ComposeInsertResult res;
10113 /* insert the file if the file is short or if the user confirmed that
10114 he/she wants to insert the large file */
10115 res = compose_insert_file(compose, file);
10116 if (res == COMPOSE_INSERT_READ_ERROR) {
10117 alertpanel_error(_("File '%s' could not be read."), shortfile);
10118 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
10119 alertpanel_error(_("File '%s' contained invalid characters\n"
10120 "for the current encoding, insertion may be incorrect."),
10122 } else if (res == COMPOSE_INSERT_SUCCESS)
10129 g_list_free(file_list);
10133 if (files_inserted > 0 && compose->gtkaspell &&
10134 compose->gtkaspell->check_while_typing)
10135 gtkaspell_highlight_all(compose->gtkaspell);
10139 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10141 Compose *compose = (Compose *)data;
10143 compose_insert_sig(compose, FALSE);
10146 static void compose_replace_sig_cb(GtkAction *action, gpointer data)
10148 Compose *compose = (Compose *)data;
10150 compose_insert_sig(compose, TRUE);
10153 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10157 Compose *compose = (Compose *)data;
10159 gtkut_widget_get_uposition(widget, &x, &y);
10160 if (!compose->batch) {
10161 prefs_common.compose_x = x;
10162 prefs_common.compose_y = y;
10164 if (compose->sending || compose->updating)
10166 compose_close_cb(NULL, compose);
10170 void compose_close_toolbar(Compose *compose)
10172 compose_close_cb(NULL, compose);
10175 static gboolean compose_can_autosave(Compose *compose)
10177 if (compose->privacy_system && compose->use_encryption)
10178 return prefs_common.autosave && prefs_common.autosave_encrypted;
10180 return prefs_common.autosave;
10183 static void compose_close_cb(GtkAction *action, gpointer data)
10185 Compose *compose = (Compose *)data;
10189 if (compose->exteditor_tag != -1) {
10190 if (!compose_ext_editor_kill(compose))
10195 if (compose->modified) {
10196 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10197 if (!g_mutex_trylock(compose->mutex)) {
10198 /* we don't want to lock the mutex once it's available,
10199 * because as the only other part of compose.c locking
10200 * it is compose_close - which means once unlocked,
10201 * the compose struct will be freed */
10202 debug_print("couldn't lock mutex, probably sending\n");
10206 val = alertpanel(_("Discard message"),
10207 _("This message has been modified. Discard it?"),
10208 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10210 val = alertpanel(_("Save changes"),
10211 _("This message has been modified. Save the latest changes?"),
10212 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10214 g_mutex_unlock(compose->mutex);
10216 case G_ALERTDEFAULT:
10217 if (compose_can_autosave(compose) && !reedit)
10218 compose_remove_draft(compose);
10220 case G_ALERTALTERNATE:
10221 compose_draft(data, COMPOSE_QUIT_EDITING);
10228 compose_close(compose);
10231 static void compose_print_cb(GtkAction *action, gpointer data)
10233 Compose *compose = (Compose *) data;
10235 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10236 if (compose->targetinfo)
10237 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10240 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10242 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10243 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10244 Compose *compose = (Compose *) data;
10247 compose->out_encoding = (CharSet)value;
10250 static void compose_address_cb(GtkAction *action, gpointer data)
10252 Compose *compose = (Compose *)data;
10254 #ifndef USE_NEW_ADDRBOOK
10255 addressbook_open(compose);
10257 GError* error = NULL;
10258 addressbook_connect_signals(compose);
10259 addressbook_dbus_open(TRUE, &error);
10261 g_warning("%s", error->message);
10262 g_error_free(error);
10267 static void about_show_cb(GtkAction *action, gpointer data)
10272 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10274 Compose *compose = (Compose *)data;
10279 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10280 cm_return_if_fail(tmpl != NULL);
10282 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10284 val = alertpanel(_("Apply template"), msg,
10285 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10288 if (val == G_ALERTDEFAULT)
10289 compose_template_apply(compose, tmpl, TRUE);
10290 else if (val == G_ALERTALTERNATE)
10291 compose_template_apply(compose, tmpl, FALSE);
10294 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10296 Compose *compose = (Compose *)data;
10298 compose_exec_ext_editor(compose);
10301 static void compose_undo_cb(GtkAction *action, gpointer data)
10303 Compose *compose = (Compose *)data;
10304 gboolean prev_autowrap = compose->autowrap;
10306 compose->autowrap = FALSE;
10307 undo_undo(compose->undostruct);
10308 compose->autowrap = prev_autowrap;
10311 static void compose_redo_cb(GtkAction *action, gpointer data)
10313 Compose *compose = (Compose *)data;
10314 gboolean prev_autowrap = compose->autowrap;
10316 compose->autowrap = FALSE;
10317 undo_redo(compose->undostruct);
10318 compose->autowrap = prev_autowrap;
10321 static void entry_cut_clipboard(GtkWidget *entry)
10323 if (GTK_IS_EDITABLE(entry))
10324 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10325 else if (GTK_IS_TEXT_VIEW(entry))
10326 gtk_text_buffer_cut_clipboard(
10327 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10328 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10332 static void entry_copy_clipboard(GtkWidget *entry)
10334 if (GTK_IS_EDITABLE(entry))
10335 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10336 else if (GTK_IS_TEXT_VIEW(entry))
10337 gtk_text_buffer_copy_clipboard(
10338 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10339 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10342 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10343 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10345 if (GTK_IS_TEXT_VIEW(entry)) {
10346 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10347 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10348 GtkTextIter start_iter, end_iter;
10350 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10352 if (contents == NULL)
10355 /* we shouldn't delete the selection when middle-click-pasting, or we
10356 * can't mid-click-paste our own selection */
10357 if (clip != GDK_SELECTION_PRIMARY) {
10358 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10359 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10362 if (insert_place == NULL) {
10363 /* if insert_place isn't specified, insert at the cursor.
10364 * used for Ctrl-V pasting */
10365 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10366 start = gtk_text_iter_get_offset(&start_iter);
10367 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10369 /* if insert_place is specified, paste here.
10370 * used for mid-click-pasting */
10371 start = gtk_text_iter_get_offset(insert_place);
10372 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10373 if (prefs_common.primary_paste_unselects)
10374 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10378 /* paste unwrapped: mark the paste so it's not wrapped later */
10379 end = start + strlen(contents);
10380 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10381 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10382 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10383 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10384 /* rewrap paragraph now (after a mid-click-paste) */
10385 mark_start = gtk_text_buffer_get_insert(buffer);
10386 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10387 gtk_text_iter_backward_char(&start_iter);
10388 compose_beautify_paragraph(compose, &start_iter, TRUE);
10390 } else if (GTK_IS_EDITABLE(entry))
10391 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10393 compose->modified = TRUE;
10396 static void entry_allsel(GtkWidget *entry)
10398 if (GTK_IS_EDITABLE(entry))
10399 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10400 else if (GTK_IS_TEXT_VIEW(entry)) {
10401 GtkTextIter startiter, enditer;
10402 GtkTextBuffer *textbuf;
10404 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10405 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10406 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10408 gtk_text_buffer_move_mark_by_name(textbuf,
10409 "selection_bound", &startiter);
10410 gtk_text_buffer_move_mark_by_name(textbuf,
10411 "insert", &enditer);
10415 static void compose_cut_cb(GtkAction *action, gpointer data)
10417 Compose *compose = (Compose *)data;
10418 if (compose->focused_editable
10419 #ifndef GENERIC_UMPC
10420 && gtk_widget_has_focus(compose->focused_editable)
10423 entry_cut_clipboard(compose->focused_editable);
10426 static void compose_copy_cb(GtkAction *action, gpointer data)
10428 Compose *compose = (Compose *)data;
10429 if (compose->focused_editable
10430 #ifndef GENERIC_UMPC
10431 && gtk_widget_has_focus(compose->focused_editable)
10434 entry_copy_clipboard(compose->focused_editable);
10437 static void compose_paste_cb(GtkAction *action, gpointer data)
10439 Compose *compose = (Compose *)data;
10440 gint prev_autowrap;
10441 GtkTextBuffer *buffer;
10443 if (compose->focused_editable &&
10444 #ifndef GENERIC_UMPC
10445 gtk_widget_has_focus(compose->focused_editable)
10448 entry_paste_clipboard(compose, compose->focused_editable,
10449 prefs_common.linewrap_pastes,
10450 GDK_SELECTION_CLIPBOARD, NULL);
10455 #ifndef GENERIC_UMPC
10456 gtk_widget_has_focus(compose->text) &&
10458 compose->gtkaspell &&
10459 compose->gtkaspell->check_while_typing)
10460 gtkaspell_highlight_all(compose->gtkaspell);
10464 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10466 Compose *compose = (Compose *)data;
10467 gint wrap_quote = prefs_common.linewrap_quote;
10468 if (compose->focused_editable
10469 #ifndef GENERIC_UMPC
10470 && gtk_widget_has_focus(compose->focused_editable)
10473 /* let text_insert() (called directly or at a later time
10474 * after the gtk_editable_paste_clipboard) know that
10475 * text is to be inserted as a quotation. implemented
10476 * by using a simple refcount... */
10477 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10478 G_OBJECT(compose->focused_editable),
10479 "paste_as_quotation"));
10480 g_object_set_data(G_OBJECT(compose->focused_editable),
10481 "paste_as_quotation",
10482 GINT_TO_POINTER(paste_as_quotation + 1));
10483 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10484 entry_paste_clipboard(compose, compose->focused_editable,
10485 prefs_common.linewrap_pastes,
10486 GDK_SELECTION_CLIPBOARD, NULL);
10487 prefs_common.linewrap_quote = wrap_quote;
10491 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10493 Compose *compose = (Compose *)data;
10494 gint prev_autowrap;
10495 GtkTextBuffer *buffer;
10497 if (compose->focused_editable
10498 #ifndef GENERIC_UMPC
10499 && gtk_widget_has_focus(compose->focused_editable)
10502 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10503 GDK_SELECTION_CLIPBOARD, NULL);
10508 #ifndef GENERIC_UMPC
10509 gtk_widget_has_focus(compose->text) &&
10511 compose->gtkaspell &&
10512 compose->gtkaspell->check_while_typing)
10513 gtkaspell_highlight_all(compose->gtkaspell);
10517 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10519 Compose *compose = (Compose *)data;
10520 gint prev_autowrap;
10521 GtkTextBuffer *buffer;
10523 if (compose->focused_editable
10524 #ifndef GENERIC_UMPC
10525 && gtk_widget_has_focus(compose->focused_editable)
10528 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10529 GDK_SELECTION_CLIPBOARD, NULL);
10534 #ifndef GENERIC_UMPC
10535 gtk_widget_has_focus(compose->text) &&
10537 compose->gtkaspell &&
10538 compose->gtkaspell->check_while_typing)
10539 gtkaspell_highlight_all(compose->gtkaspell);
10543 static void compose_allsel_cb(GtkAction *action, gpointer data)
10545 Compose *compose = (Compose *)data;
10546 if (compose->focused_editable
10547 #ifndef GENERIC_UMPC
10548 && gtk_widget_has_focus(compose->focused_editable)
10551 entry_allsel(compose->focused_editable);
10554 static void textview_move_beginning_of_line (GtkTextView *text)
10556 GtkTextBuffer *buffer;
10560 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10562 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10563 mark = gtk_text_buffer_get_insert(buffer);
10564 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10565 gtk_text_iter_set_line_offset(&ins, 0);
10566 gtk_text_buffer_place_cursor(buffer, &ins);
10569 static void textview_move_forward_character (GtkTextView *text)
10571 GtkTextBuffer *buffer;
10575 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10577 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10578 mark = gtk_text_buffer_get_insert(buffer);
10579 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10580 if (gtk_text_iter_forward_cursor_position(&ins))
10581 gtk_text_buffer_place_cursor(buffer, &ins);
10584 static void textview_move_backward_character (GtkTextView *text)
10586 GtkTextBuffer *buffer;
10590 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10592 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10593 mark = gtk_text_buffer_get_insert(buffer);
10594 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10595 if (gtk_text_iter_backward_cursor_position(&ins))
10596 gtk_text_buffer_place_cursor(buffer, &ins);
10599 static void textview_move_forward_word (GtkTextView *text)
10601 GtkTextBuffer *buffer;
10606 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10608 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10609 mark = gtk_text_buffer_get_insert(buffer);
10610 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10611 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10612 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10613 gtk_text_iter_backward_word_start(&ins);
10614 gtk_text_buffer_place_cursor(buffer, &ins);
10618 static void textview_move_backward_word (GtkTextView *text)
10620 GtkTextBuffer *buffer;
10624 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10626 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10627 mark = gtk_text_buffer_get_insert(buffer);
10628 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10629 if (gtk_text_iter_backward_word_starts(&ins, 1))
10630 gtk_text_buffer_place_cursor(buffer, &ins);
10633 static void textview_move_end_of_line (GtkTextView *text)
10635 GtkTextBuffer *buffer;
10639 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10641 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10642 mark = gtk_text_buffer_get_insert(buffer);
10643 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10644 if (gtk_text_iter_forward_to_line_end(&ins))
10645 gtk_text_buffer_place_cursor(buffer, &ins);
10648 static void textview_move_next_line (GtkTextView *text)
10650 GtkTextBuffer *buffer;
10655 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10657 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10658 mark = gtk_text_buffer_get_insert(buffer);
10659 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10660 offset = gtk_text_iter_get_line_offset(&ins);
10661 if (gtk_text_iter_forward_line(&ins)) {
10662 gtk_text_iter_set_line_offset(&ins, offset);
10663 gtk_text_buffer_place_cursor(buffer, &ins);
10667 static void textview_move_previous_line (GtkTextView *text)
10669 GtkTextBuffer *buffer;
10674 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10676 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10677 mark = gtk_text_buffer_get_insert(buffer);
10678 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10679 offset = gtk_text_iter_get_line_offset(&ins);
10680 if (gtk_text_iter_backward_line(&ins)) {
10681 gtk_text_iter_set_line_offset(&ins, offset);
10682 gtk_text_buffer_place_cursor(buffer, &ins);
10686 static void textview_delete_forward_character (GtkTextView *text)
10688 GtkTextBuffer *buffer;
10690 GtkTextIter ins, end_iter;
10692 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10694 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10695 mark = gtk_text_buffer_get_insert(buffer);
10696 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10698 if (gtk_text_iter_forward_char(&end_iter)) {
10699 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10703 static void textview_delete_backward_character (GtkTextView *text)
10705 GtkTextBuffer *buffer;
10707 GtkTextIter ins, end_iter;
10709 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10711 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10712 mark = gtk_text_buffer_get_insert(buffer);
10713 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10715 if (gtk_text_iter_backward_char(&end_iter)) {
10716 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10720 static void textview_delete_forward_word (GtkTextView *text)
10722 GtkTextBuffer *buffer;
10724 GtkTextIter ins, end_iter;
10726 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10728 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10729 mark = gtk_text_buffer_get_insert(buffer);
10730 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10732 if (gtk_text_iter_forward_word_end(&end_iter)) {
10733 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10737 static void textview_delete_backward_word (GtkTextView *text)
10739 GtkTextBuffer *buffer;
10741 GtkTextIter ins, end_iter;
10743 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10745 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10746 mark = gtk_text_buffer_get_insert(buffer);
10747 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10749 if (gtk_text_iter_backward_word_start(&end_iter)) {
10750 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10754 static void textview_delete_line (GtkTextView *text)
10756 GtkTextBuffer *buffer;
10758 GtkTextIter ins, start_iter, end_iter;
10760 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10762 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10763 mark = gtk_text_buffer_get_insert(buffer);
10764 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10767 gtk_text_iter_set_line_offset(&start_iter, 0);
10770 if (gtk_text_iter_ends_line(&end_iter)){
10771 if (!gtk_text_iter_forward_char(&end_iter))
10772 gtk_text_iter_backward_char(&start_iter);
10775 gtk_text_iter_forward_to_line_end(&end_iter);
10776 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10779 static void textview_delete_to_line_end (GtkTextView *text)
10781 GtkTextBuffer *buffer;
10783 GtkTextIter ins, end_iter;
10785 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10787 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10788 mark = gtk_text_buffer_get_insert(buffer);
10789 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10791 if (gtk_text_iter_ends_line(&end_iter))
10792 gtk_text_iter_forward_char(&end_iter);
10794 gtk_text_iter_forward_to_line_end(&end_iter);
10795 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10798 #define DO_ACTION(name, act) { \
10799 if(!strcmp(name, a_name)) { \
10803 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10805 const gchar *a_name = gtk_action_get_name(action);
10806 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10807 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10808 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10809 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10810 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10811 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10812 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10813 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10814 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10815 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10816 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10817 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10818 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10819 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10823 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10825 Compose *compose = (Compose *)data;
10826 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10827 ComposeCallAdvancedAction action = -1;
10829 action = compose_call_advanced_action_from_path(gaction);
10832 void (*do_action) (GtkTextView *text);
10833 } action_table[] = {
10834 {textview_move_beginning_of_line},
10835 {textview_move_forward_character},
10836 {textview_move_backward_character},
10837 {textview_move_forward_word},
10838 {textview_move_backward_word},
10839 {textview_move_end_of_line},
10840 {textview_move_next_line},
10841 {textview_move_previous_line},
10842 {textview_delete_forward_character},
10843 {textview_delete_backward_character},
10844 {textview_delete_forward_word},
10845 {textview_delete_backward_word},
10846 {textview_delete_line},
10847 {textview_delete_to_line_end}
10850 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10852 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10853 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10854 if (action_table[action].do_action)
10855 action_table[action].do_action(text);
10857 g_warning("Not implemented yet.");
10861 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10863 GtkAllocation allocation;
10867 if (GTK_IS_EDITABLE(widget)) {
10868 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10869 gtk_editable_set_position(GTK_EDITABLE(widget),
10872 if ((parent = gtk_widget_get_parent(widget))
10873 && (parent = gtk_widget_get_parent(parent))
10874 && (parent = gtk_widget_get_parent(parent))) {
10875 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10876 gtk_widget_get_allocation(widget, &allocation);
10877 gint y = allocation.y;
10878 gint height = allocation.height;
10879 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10880 (GTK_SCROLLED_WINDOW(parent));
10882 gfloat value = gtk_adjustment_get_value(shown);
10883 gfloat upper = gtk_adjustment_get_upper(shown);
10884 gfloat page_size = gtk_adjustment_get_page_size(shown);
10885 if (y < (int)value) {
10886 gtk_adjustment_set_value(shown, y - 1);
10888 if ((y + height) > ((int)value + (int)page_size)) {
10889 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10890 gtk_adjustment_set_value(shown,
10891 y + height - (int)page_size - 1);
10893 gtk_adjustment_set_value(shown,
10894 (int)upper - (int)page_size - 1);
10901 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10902 compose->focused_editable = widget;
10904 #ifdef GENERIC_UMPC
10905 if (GTK_IS_TEXT_VIEW(widget)
10906 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10907 g_object_ref(compose->notebook);
10908 g_object_ref(compose->edit_vbox);
10909 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10910 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10911 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10912 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10913 g_object_unref(compose->notebook);
10914 g_object_unref(compose->edit_vbox);
10915 g_signal_handlers_block_by_func(G_OBJECT(widget),
10916 G_CALLBACK(compose_grab_focus_cb),
10918 gtk_widget_grab_focus(widget);
10919 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10920 G_CALLBACK(compose_grab_focus_cb),
10922 } else if (!GTK_IS_TEXT_VIEW(widget)
10923 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10924 g_object_ref(compose->notebook);
10925 g_object_ref(compose->edit_vbox);
10926 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10927 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10928 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10929 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10930 g_object_unref(compose->notebook);
10931 g_object_unref(compose->edit_vbox);
10932 g_signal_handlers_block_by_func(G_OBJECT(widget),
10933 G_CALLBACK(compose_grab_focus_cb),
10935 gtk_widget_grab_focus(widget);
10936 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10937 G_CALLBACK(compose_grab_focus_cb),
10943 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10945 compose->modified = TRUE;
10946 // compose_beautify_paragraph(compose, NULL, TRUE);
10947 #ifndef GENERIC_UMPC
10948 compose_set_title(compose);
10952 static void compose_wrap_cb(GtkAction *action, gpointer data)
10954 Compose *compose = (Compose *)data;
10955 compose_beautify_paragraph(compose, NULL, TRUE);
10958 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10960 Compose *compose = (Compose *)data;
10961 compose_wrap_all_full(compose, TRUE);
10964 static void compose_find_cb(GtkAction *action, gpointer data)
10966 Compose *compose = (Compose *)data;
10968 message_search_compose(compose);
10971 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10974 Compose *compose = (Compose *)data;
10975 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10976 if (compose->autowrap)
10977 compose_wrap_all_full(compose, TRUE);
10978 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10981 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10984 Compose *compose = (Compose *)data;
10985 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10988 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10990 Compose *compose = (Compose *)data;
10992 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10995 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10997 Compose *compose = (Compose *)data;
10999 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
11002 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
11004 g_free(compose->privacy_system);
11006 compose->privacy_system = g_strdup(account->default_privacy_system);
11007 compose_update_privacy_system_menu_item(compose, warn);
11010 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
11012 Compose *compose = (Compose *)data;
11014 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
11015 gtk_widget_show(compose->ruler_hbox);
11016 prefs_common.show_ruler = TRUE;
11018 gtk_widget_hide(compose->ruler_hbox);
11019 gtk_widget_queue_resize(compose->edit_vbox);
11020 prefs_common.show_ruler = FALSE;
11024 static void compose_attach_drag_received_cb (GtkWidget *widget,
11025 GdkDragContext *context,
11028 GtkSelectionData *data,
11031 gpointer user_data)
11033 Compose *compose = (Compose *)user_data;
11037 type = gtk_selection_data_get_data_type(data);
11038 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
11040 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
11042 ) && gtk_drag_get_source_widget(context) !=
11043 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11044 list = uri_list_extract_filenames(
11045 (const gchar *)gtk_selection_data_get_data(data));
11046 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11047 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
11048 compose_attach_append
11049 (compose, (const gchar *)tmp->data,
11050 utf8_filename, NULL, NULL);
11051 g_free(utf8_filename);
11053 if (list) compose_changed_cb(NULL, compose);
11054 list_free_strings(list);
11056 } else if (gtk_drag_get_source_widget(context)
11057 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
11058 /* comes from our summaryview */
11059 SummaryView * summaryview = NULL;
11060 GSList * list = NULL, *cur = NULL;
11062 if (mainwindow_get_mainwindow())
11063 summaryview = mainwindow_get_mainwindow()->summaryview;
11066 list = summary_get_selected_msg_list(summaryview);
11068 for (cur = list; cur; cur = cur->next) {
11069 MsgInfo *msginfo = (MsgInfo *)cur->data;
11070 gchar *file = NULL;
11072 file = procmsg_get_message_file_full(msginfo,
11075 compose_attach_append(compose, (const gchar *)file,
11076 (const gchar *)file, "message/rfc822", NULL);
11080 g_slist_free(list);
11084 static gboolean compose_drag_drop(GtkWidget *widget,
11085 GdkDragContext *drag_context,
11087 guint time, gpointer user_data)
11089 /* not handling this signal makes compose_insert_drag_received_cb
11094 static gboolean completion_set_focus_to_subject
11095 (GtkWidget *widget,
11096 GdkEventKey *event,
11099 cm_return_val_if_fail(compose != NULL, FALSE);
11101 /* make backtab move to subject field */
11102 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
11103 gtk_widget_grab_focus(compose->subject_entry);
11109 static void compose_insert_drag_received_cb (GtkWidget *widget,
11110 GdkDragContext *drag_context,
11113 GtkSelectionData *data,
11116 gpointer user_data)
11118 Compose *compose = (Compose *)user_data;
11122 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11124 type = gtk_selection_data_get_data_type(data);
11126 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
11128 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
11130 AlertValue val = G_ALERTDEFAULT;
11131 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
11133 list = uri_list_extract_filenames(ddata);
11134 if (list == NULL && strstr(ddata, "://")) {
11135 /* Assume a list of no files, and data has ://, is a remote link */
11136 gchar *tmpdata = g_strstrip(g_strdup(ddata));
11137 gchar *tmpfile = get_tmp_file();
11138 str_write_to_file(tmpdata, tmpfile);
11140 compose_insert_file(compose, tmpfile);
11141 claws_unlink(tmpfile);
11143 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11144 compose_beautify_paragraph(compose, NULL, TRUE);
11147 switch (prefs_common.compose_dnd_mode) {
11148 case COMPOSE_DND_ASK:
11149 val = alertpanel_full(_("Insert or attach?"),
11150 _("Do you want to insert the contents of the file(s) "
11151 "into the message body, or attach it to the email?"),
11152 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11153 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11155 case COMPOSE_DND_INSERT:
11156 val = G_ALERTALTERNATE;
11158 case COMPOSE_DND_ATTACH:
11159 val = G_ALERTOTHER;
11162 /* unexpected case */
11163 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11166 if (val & G_ALERTDISABLE) {
11167 val &= ~G_ALERTDISABLE;
11168 /* remember what action to perform by default, only if we don't click Cancel */
11169 if (val == G_ALERTALTERNATE)
11170 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11171 else if (val == G_ALERTOTHER)
11172 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11175 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11176 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11177 list_free_strings(list);
11180 } else if (val == G_ALERTOTHER) {
11181 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11182 list_free_strings(list);
11187 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11188 compose_insert_file(compose, (const gchar *)tmp->data);
11190 list_free_strings(list);
11192 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11197 static void compose_header_drag_received_cb (GtkWidget *widget,
11198 GdkDragContext *drag_context,
11201 GtkSelectionData *data,
11204 gpointer user_data)
11206 GtkEditable *entry = (GtkEditable *)user_data;
11207 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11209 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11212 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11213 gchar *decoded=g_new(gchar, strlen(email));
11216 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11217 gtk_editable_delete_text(entry, 0, -1);
11218 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11219 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11223 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11226 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11228 Compose *compose = (Compose *)data;
11230 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11231 compose->return_receipt = TRUE;
11233 compose->return_receipt = FALSE;
11236 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11238 Compose *compose = (Compose *)data;
11240 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11241 compose->remove_references = TRUE;
11243 compose->remove_references = FALSE;
11246 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11247 ComposeHeaderEntry *headerentry)
11249 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11253 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11254 GdkEventKey *event,
11255 ComposeHeaderEntry *headerentry)
11257 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11258 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11259 !(event->state & GDK_MODIFIER_MASK) &&
11260 (event->keyval == GDK_KEY_BackSpace) &&
11261 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11262 gtk_container_remove
11263 (GTK_CONTAINER(headerentry->compose->header_table),
11264 headerentry->combo);
11265 gtk_container_remove
11266 (GTK_CONTAINER(headerentry->compose->header_table),
11267 headerentry->entry);
11268 headerentry->compose->header_list =
11269 g_slist_remove(headerentry->compose->header_list,
11271 g_free(headerentry);
11272 } else if (event->keyval == GDK_KEY_Tab) {
11273 if (headerentry->compose->header_last == headerentry) {
11274 /* Override default next focus, and give it to subject_entry
11275 * instead of notebook tabs
11277 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11278 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11285 static gboolean scroll_postpone(gpointer data)
11287 Compose *compose = (Compose *)data;
11289 if (compose->batch)
11292 GTK_EVENTS_FLUSH();
11293 compose_show_first_last_header(compose, FALSE);
11297 static void compose_headerentry_changed_cb(GtkWidget *entry,
11298 ComposeHeaderEntry *headerentry)
11300 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11301 compose_create_header_entry(headerentry->compose);
11302 g_signal_handlers_disconnect_matched
11303 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11304 0, 0, NULL, NULL, headerentry);
11306 if (!headerentry->compose->batch)
11307 g_timeout_add(0, scroll_postpone, headerentry->compose);
11311 static gboolean compose_defer_auto_save_draft(Compose *compose)
11313 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
11314 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11318 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11320 GtkAdjustment *vadj;
11322 cm_return_if_fail(compose);
11327 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11328 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11329 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11330 gtk_widget_get_parent(compose->header_table)));
11331 gtk_adjustment_set_value(vadj, (show_first ?
11332 gtk_adjustment_get_lower(vadj) :
11333 (gtk_adjustment_get_upper(vadj) -
11334 gtk_adjustment_get_page_size(vadj))));
11335 gtk_adjustment_changed(vadj);
11338 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11339 const gchar *text, gint len, Compose *compose)
11341 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11342 (G_OBJECT(compose->text), "paste_as_quotation"));
11345 cm_return_if_fail(text != NULL);
11347 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11348 G_CALLBACK(text_inserted),
11350 if (paste_as_quotation) {
11352 const gchar *qmark;
11354 GtkTextIter start_iter;
11357 len = strlen(text);
11359 new_text = g_strndup(text, len);
11361 qmark = compose_quote_char_from_context(compose);
11363 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11364 gtk_text_buffer_place_cursor(buffer, iter);
11366 pos = gtk_text_iter_get_offset(iter);
11368 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11369 _("Quote format error at line %d."));
11370 quote_fmt_reset_vartable();
11372 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11373 GINT_TO_POINTER(paste_as_quotation - 1));
11375 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11376 gtk_text_buffer_place_cursor(buffer, iter);
11377 gtk_text_buffer_delete_mark(buffer, mark);
11379 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11380 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11381 compose_beautify_paragraph(compose, &start_iter, FALSE);
11382 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11383 gtk_text_buffer_delete_mark(buffer, mark);
11385 if (strcmp(text, "\n") || compose->automatic_break
11386 || gtk_text_iter_starts_line(iter)) {
11387 GtkTextIter before_ins;
11388 gtk_text_buffer_insert(buffer, iter, text, len);
11389 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11390 before_ins = *iter;
11391 gtk_text_iter_backward_chars(&before_ins, len);
11392 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11395 /* check if the preceding is just whitespace or quote */
11396 GtkTextIter start_line;
11397 gchar *tmp = NULL, *quote = NULL;
11398 gint quote_len = 0, is_normal = 0;
11399 start_line = *iter;
11400 gtk_text_iter_set_line_offset(&start_line, 0);
11401 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11404 if (*tmp == '\0') {
11407 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11415 gtk_text_buffer_insert(buffer, iter, text, len);
11417 gtk_text_buffer_insert_with_tags_by_name(buffer,
11418 iter, text, len, "no_join", NULL);
11423 if (!paste_as_quotation) {
11424 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11425 compose_beautify_paragraph(compose, iter, FALSE);
11426 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11427 gtk_text_buffer_delete_mark(buffer, mark);
11430 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11431 G_CALLBACK(text_inserted),
11433 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11435 if (compose_can_autosave(compose) &&
11436 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11437 compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
11438 compose->draft_timeout_tag = g_timeout_add
11439 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11443 static void compose_check_all(GtkAction *action, gpointer data)
11445 Compose *compose = (Compose *)data;
11446 if (!compose->gtkaspell)
11449 if (gtk_widget_has_focus(compose->subject_entry))
11450 claws_spell_entry_check_all(
11451 CLAWS_SPELL_ENTRY(compose->subject_entry));
11453 gtkaspell_check_all(compose->gtkaspell);
11456 static void compose_highlight_all(GtkAction *action, gpointer data)
11458 Compose *compose = (Compose *)data;
11459 if (compose->gtkaspell) {
11460 claws_spell_entry_recheck_all(
11461 CLAWS_SPELL_ENTRY(compose->subject_entry));
11462 gtkaspell_highlight_all(compose->gtkaspell);
11466 static void compose_check_backwards(GtkAction *action, gpointer data)
11468 Compose *compose = (Compose *)data;
11469 if (!compose->gtkaspell) {
11470 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11474 if (gtk_widget_has_focus(compose->subject_entry))
11475 claws_spell_entry_check_backwards(
11476 CLAWS_SPELL_ENTRY(compose->subject_entry));
11478 gtkaspell_check_backwards(compose->gtkaspell);
11481 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11483 Compose *compose = (Compose *)data;
11484 if (!compose->gtkaspell) {
11485 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11489 if (gtk_widget_has_focus(compose->subject_entry))
11490 claws_spell_entry_check_forwards_go(
11491 CLAWS_SPELL_ENTRY(compose->subject_entry));
11493 gtkaspell_check_forwards_go(compose->gtkaspell);
11498 *\brief Guess originating forward account from MsgInfo and several
11499 * "common preference" settings. Return NULL if no guess.
11501 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11503 PrefsAccount *account = NULL;
11505 cm_return_val_if_fail(msginfo, NULL);
11506 cm_return_val_if_fail(msginfo->folder, NULL);
11507 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11509 if (msginfo->folder->prefs->enable_default_account)
11510 account = account_find_from_id(msginfo->folder->prefs->default_account);
11513 account = msginfo->folder->folder->account;
11515 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11517 Xstrdup_a(to, msginfo->to, return NULL);
11518 extract_address(to);
11519 account = account_find_from_address(to, FALSE);
11522 if (!account && prefs_common.forward_account_autosel) {
11523 gchar cc[BUFFSIZE];
11524 if (!procheader_get_header_from_msginfo
11525 (msginfo, cc,sizeof cc , "Cc:")) {
11526 gchar *buf = cc + strlen("Cc:");
11527 extract_address(buf);
11528 account = account_find_from_address(buf, FALSE);
11532 if (!account && prefs_common.forward_account_autosel) {
11533 gchar deliveredto[BUFFSIZE];
11534 if (!procheader_get_header_from_msginfo
11535 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11536 gchar *buf = deliveredto + strlen("Delivered-To:");
11537 extract_address(buf);
11538 account = account_find_from_address(buf, FALSE);
11545 gboolean compose_close(Compose *compose)
11549 cm_return_val_if_fail(compose, FALSE);
11551 if (!g_mutex_trylock(compose->mutex)) {
11552 /* we have to wait for the (possibly deferred by auto-save)
11553 * drafting to be done, before destroying the compose under
11555 debug_print("waiting for drafting to finish...\n");
11556 compose_allow_user_actions(compose, FALSE);
11557 if (compose->close_timeout_tag == 0) {
11558 compose->close_timeout_tag =
11559 g_timeout_add (500, (GSourceFunc) compose_close,
11565 if (compose->draft_timeout_tag >= 0) {
11566 g_source_remove(compose->draft_timeout_tag);
11567 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;
11570 gtkut_widget_get_uposition(compose->window, &x, &y);
11571 if (!compose->batch) {
11572 prefs_common.compose_x = x;
11573 prefs_common.compose_y = y;
11575 g_mutex_unlock(compose->mutex);
11576 compose_destroy(compose);
11581 * Add entry field for each address in list.
11582 * \param compose E-Mail composition object.
11583 * \param listAddress List of (formatted) E-Mail addresses.
11585 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11588 node = listAddress;
11590 addr = ( gchar * ) node->data;
11591 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11592 node = g_list_next( node );
11596 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11597 guint action, gboolean opening_multiple)
11599 gchar *body = NULL;
11600 GSList *new_msglist = NULL;
11601 MsgInfo *tmp_msginfo = NULL;
11602 gboolean originally_enc = FALSE;
11603 gboolean originally_sig = FALSE;
11604 Compose *compose = NULL;
11605 gchar *s_system = NULL;
11607 cm_return_if_fail(msgview != NULL);
11609 cm_return_if_fail(msginfo_list != NULL);
11611 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11612 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11613 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11615 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11616 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11617 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11618 orig_msginfo, mimeinfo);
11619 if (tmp_msginfo != NULL) {
11620 new_msglist = g_slist_append(NULL, tmp_msginfo);
11622 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11623 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11624 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11626 tmp_msginfo->folder = orig_msginfo->folder;
11627 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11628 if (orig_msginfo->tags) {
11629 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11630 tmp_msginfo->folder->tags_dirty = TRUE;
11636 if (!opening_multiple)
11637 body = messageview_get_selection(msgview);
11640 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11641 procmsg_msginfo_free(tmp_msginfo);
11642 g_slist_free(new_msglist);
11644 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11646 if (compose && originally_enc) {
11647 compose_force_encryption(compose, compose->account, FALSE, s_system);
11650 if (compose && originally_sig && compose->account->default_sign_reply) {
11651 compose_force_signing(compose, compose->account, s_system);
11655 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11658 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11661 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11662 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11663 GSList *cur = msginfo_list;
11664 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11665 "messages. Opening the windows "
11666 "could take some time. Do you "
11667 "want to continue?"),
11668 g_slist_length(msginfo_list));
11669 if (g_slist_length(msginfo_list) > 9
11670 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11671 != G_ALERTALTERNATE) {
11676 /* We'll open multiple compose windows */
11677 /* let the WM place the next windows */
11678 compose_force_window_origin = FALSE;
11679 for (; cur; cur = cur->next) {
11681 tmplist.data = cur->data;
11682 tmplist.next = NULL;
11683 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11685 compose_force_window_origin = TRUE;
11687 /* forwarding multiple mails as attachments is done via a
11688 * single compose window */
11689 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11693 void compose_check_for_email_account(Compose *compose)
11695 PrefsAccount *ac = NULL, *curr = NULL;
11701 if (compose->account && compose->account->protocol == A_NNTP) {
11702 ac = account_get_cur_account();
11703 if (ac->protocol == A_NNTP) {
11704 list = account_get_list();
11706 for( ; list != NULL ; list = g_list_next(list)) {
11707 curr = (PrefsAccount *) list->data;
11708 if (curr->protocol != A_NNTP) {
11714 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11719 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11720 const gchar *address)
11722 GSList *msginfo_list = NULL;
11723 gchar *body = messageview_get_selection(msgview);
11726 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11728 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11729 compose_check_for_email_account(compose);
11730 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11731 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11732 compose_reply_set_subject(compose, msginfo);
11735 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11738 void compose_set_position(Compose *compose, gint pos)
11740 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11742 gtkut_text_view_set_position(text, pos);
11745 gboolean compose_search_string(Compose *compose,
11746 const gchar *str, gboolean case_sens)
11748 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11750 return gtkut_text_view_search_string(text, str, case_sens);
11753 gboolean compose_search_string_backward(Compose *compose,
11754 const gchar *str, gboolean case_sens)
11756 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11758 return gtkut_text_view_search_string_backward(text, str, case_sens);
11761 /* allocate a msginfo structure and populate its data from a compose data structure */
11762 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11764 MsgInfo *newmsginfo;
11766 gchar buf[BUFFSIZE];
11768 cm_return_val_if_fail( compose != NULL, NULL );
11770 newmsginfo = procmsg_msginfo_new();
11773 get_rfc822_date(buf, sizeof(buf));
11774 newmsginfo->date = g_strdup(buf);
11777 if (compose->from_name) {
11778 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11779 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11783 if (compose->subject_entry)
11784 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11786 /* to, cc, reply-to, newsgroups */
11787 for (list = compose->header_list; list; list = list->next) {
11788 gchar *header = gtk_editable_get_chars(
11790 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11791 gchar *entry = gtk_editable_get_chars(
11792 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11794 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11795 if ( newmsginfo->to == NULL ) {
11796 newmsginfo->to = g_strdup(entry);
11797 } else if (entry && *entry) {
11798 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11799 g_free(newmsginfo->to);
11800 newmsginfo->to = tmp;
11803 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11804 if ( newmsginfo->cc == NULL ) {
11805 newmsginfo->cc = g_strdup(entry);
11806 } else if (entry && *entry) {
11807 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11808 g_free(newmsginfo->cc);
11809 newmsginfo->cc = tmp;
11812 if ( strcasecmp(header,
11813 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11814 if ( newmsginfo->newsgroups == NULL ) {
11815 newmsginfo->newsgroups = g_strdup(entry);
11816 } else if (entry && *entry) {
11817 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11818 g_free(newmsginfo->newsgroups);
11819 newmsginfo->newsgroups = tmp;
11827 /* other data is unset */
11833 /* update compose's dictionaries from folder dict settings */
11834 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11835 FolderItem *folder_item)
11837 cm_return_if_fail(compose != NULL);
11839 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11840 FolderItemPrefs *prefs = folder_item->prefs;
11842 if (prefs->enable_default_dictionary)
11843 gtkaspell_change_dict(compose->gtkaspell,
11844 prefs->default_dictionary, FALSE);
11845 if (folder_item->prefs->enable_default_alt_dictionary)
11846 gtkaspell_change_alt_dict(compose->gtkaspell,
11847 prefs->default_alt_dictionary);
11848 if (prefs->enable_default_dictionary
11849 || prefs->enable_default_alt_dictionary)
11850 compose_spell_menu_changed(compose);
11855 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11857 Compose *compose = (Compose *)data;
11859 cm_return_if_fail(compose != NULL);
11861 gtk_widget_grab_focus(compose->text);