2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
397 static void compose_close_cb (GtkAction *action,
399 static void compose_print_cb (GtkAction *action,
402 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
404 static void compose_address_cb (GtkAction *action,
406 static void about_show_cb (GtkAction *action,
408 static void compose_template_activate_cb(GtkWidget *widget,
411 static void compose_ext_editor_cb (GtkAction *action,
414 static gint compose_delete_cb (GtkWidget *widget,
418 static void compose_undo_cb (GtkAction *action,
420 static void compose_redo_cb (GtkAction *action,
422 static void compose_cut_cb (GtkAction *action,
424 static void compose_copy_cb (GtkAction *action,
426 static void compose_paste_cb (GtkAction *action,
428 static void compose_paste_as_quote_cb (GtkAction *action,
430 static void compose_paste_no_wrap_cb (GtkAction *action,
432 static void compose_paste_wrap_cb (GtkAction *action,
434 static void compose_allsel_cb (GtkAction *action,
437 static void compose_advanced_action_cb (GtkAction *action,
440 static void compose_grab_focus_cb (GtkWidget *widget,
443 static void compose_changed_cb (GtkTextBuffer *textbuf,
446 static void compose_wrap_cb (GtkAction *action,
448 static void compose_wrap_all_cb (GtkAction *action,
450 static void compose_find_cb (GtkAction *action,
452 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
454 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
457 static void compose_toggle_ruler_cb (GtkToggleAction *action,
459 static void compose_toggle_sign_cb (GtkToggleAction *action,
461 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
463 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
472 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
474 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
475 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
476 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
478 static void compose_attach_drag_received_cb (GtkWidget *widget,
479 GdkDragContext *drag_context,
482 GtkSelectionData *data,
486 static void compose_insert_drag_received_cb (GtkWidget *widget,
487 GdkDragContext *drag_context,
490 GtkSelectionData *data,
494 static void compose_header_drag_received_cb (GtkWidget *widget,
495 GdkDragContext *drag_context,
498 GtkSelectionData *data,
503 static gboolean compose_drag_drop (GtkWidget *widget,
504 GdkDragContext *drag_context,
506 guint time, gpointer user_data);
507 static gboolean completion_set_focus_to_subject
512 static void text_inserted (GtkTextBuffer *buffer,
517 static Compose *compose_generic_reply(MsgInfo *msginfo,
518 ComposeQuoteMode quote_mode,
522 gboolean followup_and_reply_to,
525 static void compose_headerentry_changed_cb (GtkWidget *entry,
526 ComposeHeaderEntry *headerentry);
527 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
529 ComposeHeaderEntry *headerentry);
530 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
531 ComposeHeaderEntry *headerentry);
533 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
535 static void compose_allow_user_actions (Compose *compose, gboolean allow);
537 static void compose_nothing_cb (GtkAction *action, gpointer data)
543 static void compose_check_all (GtkAction *action, gpointer data);
544 static void compose_highlight_all (GtkAction *action, gpointer data);
545 static void compose_check_backwards (GtkAction *action, gpointer data);
546 static void compose_check_forwards_go (GtkAction *action, gpointer data);
549 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
551 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
554 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
555 FolderItem *folder_item);
557 static void compose_attach_update_label(Compose *compose);
558 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
559 gboolean respect_default_to);
560 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
562 static GtkActionEntry compose_popup_entries[] =
564 {"Compose", NULL, "Compose" },
565 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
566 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
567 {"Compose/---", NULL, "---", NULL, NULL, NULL },
568 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
571 static GtkActionEntry compose_entries[] =
573 {"Menu", NULL, "Menu" },
575 {"Message", NULL, N_("_Message") },
576 {"Edit", NULL, N_("_Edit") },
578 {"Spelling", NULL, N_("_Spelling") },
580 {"Options", NULL, N_("_Options") },
581 {"Tools", NULL, N_("_Tools") },
582 {"Help", NULL, N_("_Help") },
584 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
585 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
586 {"Message/---", NULL, "---" },
588 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
589 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
590 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
591 /* {"Message/---", NULL, "---" }, */
592 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
593 /* {"Message/---", NULL, "---" }, */
594 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
595 /* {"Message/---", NULL, "---" }, */
596 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
599 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
600 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
601 {"Edit/---", NULL, "---" },
603 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
604 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
605 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
607 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
608 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
609 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
610 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
612 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
614 {"Edit/Advanced", NULL, N_("A_dvanced") },
615 {"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*/
616 {"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*/
617 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
618 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
619 {"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*/
620 {"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*/
621 {"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*/
622 {"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*/
623 {"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*/
624 {"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*/
625 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
626 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
627 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
628 {"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*/
630 /* {"Edit/---", NULL, "---" }, */
631 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
633 /* {"Edit/---", NULL, "---" }, */
634 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
635 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
640 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
641 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
642 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
643 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
645 {"Spelling/---", NULL, "---" },
646 {"Spelling/Options", NULL, N_("_Options") },
651 {"Options/ReplyMode", NULL, N_("Reply _mode") },
652 {"Options/---", NULL, "---" },
653 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
654 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
656 /* {"Options/---", NULL, "---" }, */
658 {"Options/Priority", NULL, N_("_Priority") },
660 {"Options/Encoding", NULL, N_("Character _encoding") },
661 {"Options/Encoding/---", NULL, "---" },
662 #define ENC_ACTION(cs_char,c_char,string) \
663 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
665 {"Options/Encoding/Western", NULL, N_("Western European") },
666 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
667 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
668 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
669 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
670 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
671 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
672 {"Options/Encoding/Korean", NULL, N_("Korean") },
673 {"Options/Encoding/Thai", NULL, N_("Thai") },
676 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
678 {"Tools/Template", NULL, N_("_Template") },
679 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
680 {"Tools/Actions", NULL, N_("Actio_ns") },
681 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
687 static GtkToggleActionEntry compose_toggle_entries[] =
689 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
690 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
691 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
692 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
693 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
694 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
695 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
698 static GtkRadioActionEntry compose_radio_rm_entries[] =
700 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
701 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
702 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
703 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
706 static GtkRadioActionEntry compose_radio_prio_entries[] =
708 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
709 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
710 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
711 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
712 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
715 static GtkRadioActionEntry compose_radio_enc_entries[] =
717 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
751 static GtkTargetEntry compose_mime_types[] =
753 {"text/uri-list", 0, 0},
754 {"UTF8_STRING", 0, 0},
758 static gboolean compose_put_existing_to_front(MsgInfo *info)
760 GList *compose_list = compose_get_compose_list();
764 for (elem = compose_list; elem != NULL && elem->data != NULL;
766 Compose *c = (Compose*)elem->data;
768 if (!c->targetinfo || !c->targetinfo->msgid ||
772 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
773 gtkut_window_popup(c->window);
781 static GdkColor quote_color1 =
782 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
783 static GdkColor quote_color2 =
784 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
785 static GdkColor quote_color3 =
786 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_bgcolor1 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 static GdkColor quote_bgcolor2 =
791 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
792 static GdkColor quote_bgcolor3 =
793 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor signature_color = {
802 static GdkColor uri_color = {
809 static void compose_create_tags(GtkTextView *text, Compose *compose)
811 GtkTextBuffer *buffer;
812 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 #if !GTK_CHECK_VERSION(2, 24, 0)
820 buffer = gtk_text_view_get_buffer(text);
822 if (prefs_common.enable_color) {
823 /* grab the quote colors, converting from an int to a GdkColor */
824 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
826 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
828 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
836 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
838 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
841 signature_color = quote_color1 = quote_color2 = quote_color3 =
842 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
845 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
846 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
847 "foreground-gdk", "e_color1,
848 "paragraph-background-gdk", "e_bgcolor1,
850 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
851 "foreground-gdk", "e_color2,
852 "paragraph-background-gdk", "e_bgcolor2,
854 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
855 "foreground-gdk", "e_color3,
856 "paragraph-background-gdk", "e_bgcolor3,
859 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
860 "foreground-gdk", "e_color1,
862 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
863 "foreground-gdk", "e_color2,
865 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
866 "foreground-gdk", "e_color3,
870 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
871 "foreground-gdk", &signature_color,
874 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
875 "foreground-gdk", &uri_color,
877 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
878 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
880 #if !GTK_CHECK_VERSION(2, 24, 0)
881 color[0] = quote_color1;
882 color[1] = quote_color2;
883 color[2] = quote_color3;
884 color[3] = quote_bgcolor1;
885 color[4] = quote_bgcolor2;
886 color[5] = quote_bgcolor3;
887 color[6] = signature_color;
888 color[7] = uri_color;
890 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
891 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
893 for (i = 0; i < 8; i++) {
894 if (success[i] == FALSE) {
895 g_warning("Compose: color allocation failed.\n");
896 quote_color1 = quote_color2 = quote_color3 =
897 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
898 signature_color = uri_color = black;
904 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
910 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
912 return compose_generic_new(account, mailto, item, NULL, NULL);
915 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
917 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
920 #define SCROLL_TO_CURSOR(compose) { \
921 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
922 gtk_text_view_get_buffer( \
923 GTK_TEXT_VIEW(compose->text))); \
924 gtk_text_view_scroll_mark_onscreen( \
925 GTK_TEXT_VIEW(compose->text), \
929 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
932 if (folderidentifier) {
933 #if !GTK_CHECK_VERSION(2, 24, 0)
934 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
936 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
938 prefs_common.compose_save_to_history = add_history(
939 prefs_common.compose_save_to_history, folderidentifier);
940 #if !GTK_CHECK_VERSION(2, 24, 0)
941 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
942 prefs_common.compose_save_to_history);
944 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
945 prefs_common.compose_save_to_history);
949 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
950 if (folderidentifier)
951 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
953 gtk_entry_set_text(GTK_ENTRY(entry), "");
956 static gchar *compose_get_save_to(Compose *compose)
959 gchar *result = NULL;
960 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
961 result = gtk_editable_get_chars(entry, 0, -1);
964 #if !GTK_CHECK_VERSION(2, 24, 0)
965 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
967 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
969 prefs_common.compose_save_to_history = add_history(
970 prefs_common.compose_save_to_history, result);
971 #if !GTK_CHECK_VERSION(2, 24, 0)
972 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
973 prefs_common.compose_save_to_history);
975 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
976 prefs_common.compose_save_to_history);
982 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
983 GList *attach_files, GList *listAddress )
986 GtkTextView *textview;
987 GtkTextBuffer *textbuf;
989 const gchar *subject_format = NULL;
990 const gchar *body_format = NULL;
991 gchar *mailto_from = NULL;
992 PrefsAccount *mailto_account = NULL;
993 MsgInfo* dummyinfo = NULL;
994 gint cursor_pos = -1;
995 MailField mfield = NO_FIELD_PRESENT;
999 /* check if mailto defines a from */
1000 if (mailto && *mailto != '\0') {
1001 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1002 /* mailto defines a from, check if we can get account prefs from it,
1003 if not, the account prefs will be guessed using other ways, but we'll keep
1006 mailto_account = account_find_from_address(mailto_from, TRUE);
1007 if (mailto_account == NULL) {
1009 Xstrdup_a(tmp_from, mailto_from, return NULL);
1010 extract_address(tmp_from);
1011 mailto_account = account_find_from_address(tmp_from, TRUE);
1015 account = mailto_account;
1018 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1019 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1020 account = account_find_from_id(item->prefs->default_account);
1022 /* if no account prefs set, fallback to the current one */
1023 if (!account) account = cur_account;
1024 cm_return_val_if_fail(account != NULL, NULL);
1026 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1028 /* override from name if mailto asked for it */
1030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1031 g_free(mailto_from);
1033 /* override from name according to folder properties */
1034 if (item && item->prefs &&
1035 item->prefs->compose_with_format &&
1036 item->prefs->compose_override_from_format &&
1037 *item->prefs->compose_override_from_format != '\0') {
1042 dummyinfo = compose_msginfo_new_from_compose(compose);
1044 /* decode \-escape sequences in the internal representation of the quote format */
1045 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1046 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1049 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1050 compose->gtkaspell);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1054 quote_fmt_scan_string(tmp);
1057 buf = quote_fmt_get_buffer();
1059 alertpanel_error(_("New message From format error."));
1061 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1062 quote_fmt_reset_vartable();
1067 compose->replyinfo = NULL;
1068 compose->fwdinfo = NULL;
1070 textview = GTK_TEXT_VIEW(compose->text);
1071 textbuf = gtk_text_view_get_buffer(textview);
1072 compose_create_tags(textview, compose);
1074 undo_block(compose->undostruct);
1076 compose_set_dictionaries_from_folder_prefs(compose, item);
1079 if (account->auto_sig)
1080 compose_insert_sig(compose, FALSE);
1081 gtk_text_buffer_get_start_iter(textbuf, &iter);
1082 gtk_text_buffer_place_cursor(textbuf, &iter);
1084 if (account->protocol != A_NNTP) {
1085 if (mailto && *mailto != '\0') {
1086 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1089 compose_set_folder_prefs(compose, item, TRUE);
1091 if (item && item->ret_rcpt) {
1092 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1095 if (mailto && *mailto != '\0') {
1096 if (!strchr(mailto, '@'))
1097 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1099 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1100 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1101 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1102 mfield = TO_FIELD_PRESENT;
1105 * CLAWS: just don't allow return receipt request, even if the user
1106 * may want to send an email. simple but foolproof.
1108 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1110 compose_add_field_list( compose, listAddress );
1112 if (item && item->prefs && item->prefs->compose_with_format) {
1113 subject_format = item->prefs->compose_subject_format;
1114 body_format = item->prefs->compose_body_format;
1115 } else if (account->compose_with_format) {
1116 subject_format = account->compose_subject_format;
1117 body_format = account->compose_body_format;
1118 } else if (prefs_common.compose_with_format) {
1119 subject_format = prefs_common.compose_subject_format;
1120 body_format = prefs_common.compose_body_format;
1123 if (subject_format || body_format) {
1126 && *subject_format != '\0' )
1128 gchar *subject = NULL;
1133 dummyinfo = compose_msginfo_new_from_compose(compose);
1135 /* decode \-escape sequences in the internal representation of the quote format */
1136 tmp = g_malloc(strlen(subject_format)+1);
1137 pref_get_unescaped_pref(tmp, subject_format);
1139 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1141 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1142 compose->gtkaspell);
1144 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1146 quote_fmt_scan_string(tmp);
1149 buf = quote_fmt_get_buffer();
1151 alertpanel_error(_("New message subject format error."));
1153 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1154 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1155 quote_fmt_reset_vartable();
1159 mfield = SUBJECT_FIELD_PRESENT;
1163 && *body_format != '\0' )
1166 GtkTextBuffer *buffer;
1167 GtkTextIter start, end;
1171 dummyinfo = compose_msginfo_new_from_compose(compose);
1173 text = GTK_TEXT_VIEW(compose->text);
1174 buffer = gtk_text_view_get_buffer(text);
1175 gtk_text_buffer_get_start_iter(buffer, &start);
1176 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1177 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1179 compose_quote_fmt(compose, dummyinfo,
1181 NULL, tmp, FALSE, TRUE,
1182 _("The body of the \"New message\" template has an error at line %d."));
1183 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1184 quote_fmt_reset_vartable();
1188 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1189 gtkaspell_highlight_all(compose->gtkaspell);
1191 mfield = BODY_FIELD_PRESENT;
1195 procmsg_msginfo_free( dummyinfo );
1201 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1202 ainfo = (AttachInfo *) curr->data;
1203 compose_attach_append(compose, ainfo->file, ainfo->name,
1204 ainfo->content_type, ainfo->charset);
1208 compose_show_first_last_header(compose, TRUE);
1210 /* Set save folder */
1211 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1212 gchar *folderidentifier;
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1215 folderidentifier = folder_item_get_identifier(item);
1216 compose_set_save_to(compose, folderidentifier);
1217 g_free(folderidentifier);
1220 /* Place cursor according to provided input (mfield) */
1222 case NO_FIELD_PRESENT:
1223 if (compose->header_last)
1224 gtk_widget_grab_focus(compose->header_last->entry);
1226 case TO_FIELD_PRESENT:
1227 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1229 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1232 gtk_widget_grab_focus(compose->subject_entry);
1234 case SUBJECT_FIELD_PRESENT:
1235 textview = GTK_TEXT_VIEW(compose->text);
1238 textbuf = gtk_text_view_get_buffer(textview);
1241 mark = gtk_text_buffer_get_insert(textbuf);
1242 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1243 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1245 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1246 * only defers where it comes to the variable body
1247 * is not null. If no body is present compose->text
1248 * will be null in which case you cannot place the
1249 * cursor inside the component so. An empty component
1250 * is therefore created before placing the cursor
1252 case BODY_FIELD_PRESENT:
1253 cursor_pos = quote_fmt_get_cursor_pos();
1254 if (cursor_pos == -1)
1255 gtk_widget_grab_focus(compose->header_last->entry);
1257 gtk_widget_grab_focus(compose->text);
1261 undo_unblock(compose->undostruct);
1263 if (prefs_common.auto_exteditor)
1264 compose_exec_ext_editor(compose);
1266 compose->draft_timeout_tag = -1;
1267 SCROLL_TO_CURSOR(compose);
1269 compose->modified = FALSE;
1270 compose_set_title(compose);
1272 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1277 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1278 gboolean override_pref, const gchar *system)
1280 const gchar *privacy = NULL;
1282 cm_return_if_fail(compose != NULL);
1283 cm_return_if_fail(account != NULL);
1285 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1298 if (privacy != NULL) {
1300 g_free(compose->privacy_system);
1301 compose->privacy_system = NULL;
1303 if (compose->privacy_system == NULL)
1304 compose->privacy_system = g_strdup(privacy);
1305 else if (*(compose->privacy_system) == '\0') {
1306 g_free(compose->privacy_system);
1307 compose->privacy_system = g_strdup(privacy);
1309 compose_update_privacy_system_menu_item(compose, FALSE);
1310 compose_use_encryption(compose, TRUE);
1314 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1316 const gchar *privacy = NULL;
1318 if (account->default_privacy_system && strlen(account->default_privacy_system))
1319 privacy = account->default_privacy_system;
1323 GSList *privacy_avail = privacy_get_system_ids();
1324 if (privacy_avail && g_slist_length(privacy_avail)) {
1325 privacy = (gchar *)(privacy_avail->data);
1329 if (privacy != NULL) {
1331 g_free(compose->privacy_system);
1332 compose->privacy_system = NULL;
1334 if (compose->privacy_system == NULL)
1335 compose->privacy_system = g_strdup(privacy);
1336 compose_update_privacy_system_menu_item(compose, FALSE);
1337 compose_use_signing(compose, TRUE);
1341 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1345 Compose *compose = NULL;
1347 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1349 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1350 cm_return_val_if_fail(msginfo != NULL, NULL);
1352 list_len = g_slist_length(msginfo_list);
1356 case COMPOSE_REPLY_TO_ADDRESS:
1357 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1358 FALSE, prefs_common.default_reply_list, FALSE, body);
1360 case COMPOSE_REPLY_WITH_QUOTE:
1361 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1362 FALSE, prefs_common.default_reply_list, FALSE, body);
1364 case COMPOSE_REPLY_WITHOUT_QUOTE:
1365 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1366 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1368 case COMPOSE_REPLY_TO_SENDER:
1369 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1370 FALSE, FALSE, TRUE, body);
1372 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1373 compose = compose_followup_and_reply_to(msginfo,
1374 COMPOSE_QUOTE_CHECK,
1375 FALSE, FALSE, body);
1377 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1379 FALSE, FALSE, TRUE, body);
1381 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1383 FALSE, FALSE, TRUE, NULL);
1385 case COMPOSE_REPLY_TO_ALL:
1386 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1387 TRUE, FALSE, FALSE, body);
1389 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1391 TRUE, FALSE, FALSE, body);
1393 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1395 TRUE, FALSE, FALSE, NULL);
1397 case COMPOSE_REPLY_TO_LIST:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1399 FALSE, TRUE, FALSE, body);
1401 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1403 FALSE, TRUE, FALSE, body);
1405 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1407 FALSE, TRUE, FALSE, NULL);
1409 case COMPOSE_FORWARD:
1410 if (prefs_common.forward_as_attachment) {
1411 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1414 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1418 case COMPOSE_FORWARD_INLINE:
1419 /* check if we reply to more than one Message */
1420 if (list_len == 1) {
1421 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1424 /* more messages FALL THROUGH */
1425 case COMPOSE_FORWARD_AS_ATTACH:
1426 compose = compose_forward_multiple(NULL, msginfo_list);
1428 case COMPOSE_REDIRECT:
1429 compose = compose_redirect(NULL, msginfo, FALSE);
1432 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1435 if (compose == NULL) {
1436 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1440 compose->rmode = mode;
1441 switch (compose->rmode) {
1443 case COMPOSE_REPLY_WITH_QUOTE:
1444 case COMPOSE_REPLY_WITHOUT_QUOTE:
1445 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1446 debug_print("reply mode Normal\n");
1447 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1448 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1450 case COMPOSE_REPLY_TO_SENDER:
1451 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1452 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1453 debug_print("reply mode Sender\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1456 case COMPOSE_REPLY_TO_ALL:
1457 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1458 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1459 debug_print("reply mode All\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1462 case COMPOSE_REPLY_TO_LIST:
1463 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1464 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1465 debug_print("reply mode List\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1468 case COMPOSE_REPLY_TO_ADDRESS:
1469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1477 static Compose *compose_reply(MsgInfo *msginfo,
1478 ComposeQuoteMode quote_mode,
1484 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1485 to_sender, FALSE, body);
1488 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1489 ComposeQuoteMode quote_mode,
1494 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1495 to_sender, TRUE, body);
1498 static void compose_extract_original_charset(Compose *compose)
1500 MsgInfo *info = NULL;
1501 if (compose->replyinfo) {
1502 info = compose->replyinfo;
1503 } else if (compose->fwdinfo) {
1504 info = compose->fwdinfo;
1505 } else if (compose->targetinfo) {
1506 info = compose->targetinfo;
1509 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1510 MimeInfo *partinfo = mimeinfo;
1511 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1512 partinfo = procmime_mimeinfo_next(partinfo);
1514 compose->orig_charset =
1515 g_strdup(procmime_mimeinfo_get_parameter(
1516 partinfo, "charset"));
1518 procmime_mimeinfo_free_all(mimeinfo);
1522 #define SIGNAL_BLOCK(buffer) { \
1523 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1524 G_CALLBACK(compose_changed_cb), \
1526 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(text_inserted), \
1531 #define SIGNAL_UNBLOCK(buffer) { \
1532 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1535 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1540 static Compose *compose_generic_reply(MsgInfo *msginfo,
1541 ComposeQuoteMode quote_mode,
1542 gboolean to_all, gboolean to_ml,
1544 gboolean followup_and_reply_to,
1548 PrefsAccount *account = NULL;
1549 GtkTextView *textview;
1550 GtkTextBuffer *textbuf;
1551 gboolean quote = FALSE;
1552 const gchar *qmark = NULL;
1553 const gchar *body_fmt = NULL;
1554 gchar *s_system = NULL;
1556 cm_return_val_if_fail(msginfo != NULL, NULL);
1557 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1559 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1561 cm_return_val_if_fail(account != NULL, NULL);
1563 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1565 compose->updating = TRUE;
1567 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1570 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1571 if (!compose->replyinfo)
1572 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1574 compose_extract_original_charset(compose);
1576 if (msginfo->folder && msginfo->folder->ret_rcpt)
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1579 /* Set save folder */
1580 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1581 gchar *folderidentifier;
1583 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1614 quote_fmt_scan_string(tmp);
1617 buf = quote_fmt_get_buffer();
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1627 textview = (GTK_TEXT_VIEW(compose->text));
1628 textbuf = gtk_text_view_get_buffer(textview);
1629 compose_create_tags(textview, compose);
1631 undo_block(compose->undostruct);
1633 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1634 gtkaspell_block_check(compose->gtkaspell);
1637 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1638 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1639 /* use the reply format of folder (if enabled), or the account's one
1640 (if enabled) or fallback to the global reply format, which is always
1641 enabled (even if empty), and use the relevant quotemark */
1643 if (msginfo->folder && msginfo->folder->prefs &&
1644 msginfo->folder->prefs->reply_with_format) {
1645 qmark = msginfo->folder->prefs->reply_quotemark;
1646 body_fmt = msginfo->folder->prefs->reply_body_format;
1648 } else if (account->reply_with_format) {
1649 qmark = account->reply_quotemark;
1650 body_fmt = account->reply_body_format;
1653 qmark = prefs_common.quotemark;
1654 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1655 body_fmt = gettext(prefs_common.quotefmt);
1662 /* empty quotemark is not allowed */
1663 if (qmark == NULL || *qmark == '\0')
1665 compose_quote_fmt(compose, compose->replyinfo,
1666 body_fmt, qmark, body, FALSE, TRUE,
1667 _("The body of the \"Reply\" template has an error at line %d."));
1668 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1669 quote_fmt_reset_vartable();
1672 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1673 compose_force_encryption(compose, account, FALSE, s_system);
1676 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1677 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1678 compose_force_signing(compose, account, s_system);
1682 SIGNAL_BLOCK(textbuf);
1684 if (account->auto_sig)
1685 compose_insert_sig(compose, FALSE);
1687 compose_wrap_all(compose);
1690 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1691 gtkaspell_highlight_all(compose->gtkaspell);
1692 gtkaspell_unblock_check(compose->gtkaspell);
1694 SIGNAL_UNBLOCK(textbuf);
1696 gtk_widget_grab_focus(compose->text);
1698 undo_unblock(compose->undostruct);
1700 if (prefs_common.auto_exteditor)
1701 compose_exec_ext_editor(compose);
1703 compose->modified = FALSE;
1704 compose_set_title(compose);
1706 compose->updating = FALSE;
1707 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1708 SCROLL_TO_CURSOR(compose);
1710 if (compose->deferred_destroy) {
1711 compose_destroy(compose);
1719 #define INSERT_FW_HEADER(var, hdr) \
1720 if (msginfo->var && *msginfo->var) { \
1721 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1722 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1723 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1726 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1727 gboolean as_attach, const gchar *body,
1728 gboolean no_extedit,
1732 GtkTextView *textview;
1733 GtkTextBuffer *textbuf;
1734 gint cursor_pos = -1;
1737 cm_return_val_if_fail(msginfo != NULL, NULL);
1738 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1741 !(account = compose_guess_forward_account_from_msginfo
1743 account = cur_account;
1745 if (!prefs_common.forward_as_attachment)
1746 mode = COMPOSE_FORWARD_INLINE;
1748 mode = COMPOSE_FORWARD;
1749 compose = compose_create(account, msginfo->folder, mode, batch);
1751 compose->updating = TRUE;
1752 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1753 if (!compose->fwdinfo)
1754 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1756 compose_extract_original_charset(compose);
1758 if (msginfo->subject && *msginfo->subject) {
1759 gchar *buf, *buf2, *p;
1761 buf = p = g_strdup(msginfo->subject);
1762 p += subject_get_prefix_length(p);
1763 memmove(buf, p, strlen(p) + 1);
1765 buf2 = g_strdup_printf("Fw: %s", buf);
1766 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1772 /* override from name according to folder properties */
1773 if (msginfo->folder && msginfo->folder->prefs &&
1774 msginfo->folder->prefs->forward_with_format &&
1775 msginfo->folder->prefs->forward_override_from_format &&
1776 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1780 MsgInfo *full_msginfo = NULL;
1783 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1785 full_msginfo = procmsg_msginfo_copy(msginfo);
1787 /* decode \-escape sequences in the internal representation of the quote format */
1788 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1789 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1792 gtkaspell_block_check(compose->gtkaspell);
1793 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1794 compose->gtkaspell);
1796 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1798 quote_fmt_scan_string(tmp);
1801 buf = quote_fmt_get_buffer();
1803 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1805 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1806 quote_fmt_reset_vartable();
1809 procmsg_msginfo_free(full_msginfo);
1812 textview = GTK_TEXT_VIEW(compose->text);
1813 textbuf = gtk_text_view_get_buffer(textview);
1814 compose_create_tags(textview, compose);
1816 undo_block(compose->undostruct);
1820 msgfile = procmsg_get_message_file(msginfo);
1821 if (!is_file_exist(msgfile))
1822 g_warning("%s: file not exist\n", msgfile);
1824 compose_attach_append(compose, msgfile, msgfile,
1825 "message/rfc822", NULL);
1829 const gchar *qmark = NULL;
1830 const gchar *body_fmt = NULL;
1831 MsgInfo *full_msginfo;
1833 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1835 full_msginfo = procmsg_msginfo_copy(msginfo);
1837 /* use the forward format of folder (if enabled), or the account's one
1838 (if enabled) or fallback to the global forward format, which is always
1839 enabled (even if empty), and use the relevant quotemark */
1840 if (msginfo->folder && msginfo->folder->prefs &&
1841 msginfo->folder->prefs->forward_with_format) {
1842 qmark = msginfo->folder->prefs->forward_quotemark;
1843 body_fmt = msginfo->folder->prefs->forward_body_format;
1845 } else if (account->forward_with_format) {
1846 qmark = account->forward_quotemark;
1847 body_fmt = account->forward_body_format;
1850 qmark = prefs_common.fw_quotemark;
1851 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1852 body_fmt = gettext(prefs_common.fw_quotefmt);
1857 /* empty quotemark is not allowed */
1858 if (qmark == NULL || *qmark == '\0')
1861 compose_quote_fmt(compose, full_msginfo,
1862 body_fmt, qmark, body, FALSE, TRUE,
1863 _("The body of the \"Forward\" template has an error at line %d."));
1864 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1865 quote_fmt_reset_vartable();
1866 compose_attach_parts(compose, msginfo);
1868 procmsg_msginfo_free(full_msginfo);
1871 SIGNAL_BLOCK(textbuf);
1873 if (account->auto_sig)
1874 compose_insert_sig(compose, FALSE);
1876 compose_wrap_all(compose);
1879 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1880 gtkaspell_highlight_all(compose->gtkaspell);
1881 gtkaspell_unblock_check(compose->gtkaspell);
1883 SIGNAL_UNBLOCK(textbuf);
1885 cursor_pos = quote_fmt_get_cursor_pos();
1886 if (cursor_pos == -1)
1887 gtk_widget_grab_focus(compose->header_last->entry);
1889 gtk_widget_grab_focus(compose->text);
1891 if (!no_extedit && prefs_common.auto_exteditor)
1892 compose_exec_ext_editor(compose);
1895 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1896 gchar *folderidentifier;
1898 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1899 folderidentifier = folder_item_get_identifier(msginfo->folder);
1900 compose_set_save_to(compose, folderidentifier);
1901 g_free(folderidentifier);
1904 undo_unblock(compose->undostruct);
1906 compose->modified = FALSE;
1907 compose_set_title(compose);
1909 compose->updating = FALSE;
1910 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1911 SCROLL_TO_CURSOR(compose);
1913 if (compose->deferred_destroy) {
1914 compose_destroy(compose);
1918 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1923 #undef INSERT_FW_HEADER
1925 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1928 GtkTextView *textview;
1929 GtkTextBuffer *textbuf;
1933 gboolean single_mail = TRUE;
1935 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1937 if (g_slist_length(msginfo_list) > 1)
1938 single_mail = FALSE;
1940 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1941 if (((MsgInfo *)msginfo->data)->folder == NULL)
1944 /* guess account from first selected message */
1946 !(account = compose_guess_forward_account_from_msginfo
1947 (msginfo_list->data)))
1948 account = cur_account;
1950 cm_return_val_if_fail(account != NULL, NULL);
1952 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1953 if (msginfo->data) {
1954 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1955 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1959 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1960 g_warning("no msginfo_list");
1964 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1966 compose->updating = TRUE;
1968 /* override from name according to folder properties */
1969 if (msginfo_list->data) {
1970 MsgInfo *msginfo = msginfo_list->data;
1972 if (msginfo->folder && msginfo->folder->prefs &&
1973 msginfo->folder->prefs->forward_with_format &&
1974 msginfo->folder->prefs->forward_override_from_format &&
1975 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1980 /* decode \-escape sequences in the internal representation of the quote format */
1981 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1982 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1985 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1986 compose->gtkaspell);
1988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1990 quote_fmt_scan_string(tmp);
1993 buf = quote_fmt_get_buffer();
1995 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1997 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1998 quote_fmt_reset_vartable();
2004 textview = GTK_TEXT_VIEW(compose->text);
2005 textbuf = gtk_text_view_get_buffer(textview);
2006 compose_create_tags(textview, compose);
2008 undo_block(compose->undostruct);
2009 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2010 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2012 if (!is_file_exist(msgfile))
2013 g_warning("%s: file not exist\n", msgfile);
2015 compose_attach_append(compose, msgfile, msgfile,
2016 "message/rfc822", NULL);
2021 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2022 if (info->subject && *info->subject) {
2023 gchar *buf, *buf2, *p;
2025 buf = p = g_strdup(info->subject);
2026 p += subject_get_prefix_length(p);
2027 memmove(buf, p, strlen(p) + 1);
2029 buf2 = g_strdup_printf("Fw: %s", buf);
2030 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2036 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2037 _("Fw: multiple emails"));
2040 SIGNAL_BLOCK(textbuf);
2042 if (account->auto_sig)
2043 compose_insert_sig(compose, FALSE);
2045 compose_wrap_all(compose);
2047 SIGNAL_UNBLOCK(textbuf);
2049 gtk_text_buffer_get_start_iter(textbuf, &iter);
2050 gtk_text_buffer_place_cursor(textbuf, &iter);
2052 gtk_widget_grab_focus(compose->header_last->entry);
2053 undo_unblock(compose->undostruct);
2054 compose->modified = FALSE;
2055 compose_set_title(compose);
2057 compose->updating = FALSE;
2058 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2059 SCROLL_TO_CURSOR(compose);
2061 if (compose->deferred_destroy) {
2062 compose_destroy(compose);
2066 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2071 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2073 GtkTextIter start = *iter;
2074 GtkTextIter end_iter;
2075 int start_pos = gtk_text_iter_get_offset(&start);
2077 if (!compose->account->sig_sep)
2080 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2081 start_pos+strlen(compose->account->sig_sep));
2083 /* check sig separator */
2084 str = gtk_text_iter_get_text(&start, &end_iter);
2085 if (!strcmp(str, compose->account->sig_sep)) {
2087 /* check end of line (\n) */
2088 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2089 start_pos+strlen(compose->account->sig_sep));
2090 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2091 start_pos+strlen(compose->account->sig_sep)+1);
2092 tmp = gtk_text_iter_get_text(&start, &end_iter);
2093 if (!strcmp(tmp,"\n")) {
2105 static void compose_colorize_signature(Compose *compose)
2107 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2109 GtkTextIter end_iter;
2110 gtk_text_buffer_get_start_iter(buffer, &iter);
2111 while (gtk_text_iter_forward_line(&iter))
2112 if (compose_is_sig_separator(compose, buffer, &iter)) {
2113 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2114 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2118 #define BLOCK_WRAP() { \
2119 prev_autowrap = compose->autowrap; \
2120 buffer = gtk_text_view_get_buffer( \
2121 GTK_TEXT_VIEW(compose->text)); \
2122 compose->autowrap = FALSE; \
2124 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2125 G_CALLBACK(compose_changed_cb), \
2127 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2128 G_CALLBACK(text_inserted), \
2131 #define UNBLOCK_WRAP() { \
2132 compose->autowrap = prev_autowrap; \
2133 if (compose->autowrap) { \
2134 gint old = compose->draft_timeout_tag; \
2135 compose->draft_timeout_tag = -2; \
2136 compose_wrap_all(compose); \
2137 compose->draft_timeout_tag = old; \
2140 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2141 G_CALLBACK(compose_changed_cb), \
2143 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2144 G_CALLBACK(text_inserted), \
2148 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2150 Compose *compose = NULL;
2151 PrefsAccount *account = NULL;
2152 GtkTextView *textview;
2153 GtkTextBuffer *textbuf;
2157 gchar buf[BUFFSIZE];
2158 gboolean use_signing = FALSE;
2159 gboolean use_encryption = FALSE;
2160 gchar *privacy_system = NULL;
2161 int priority = PRIORITY_NORMAL;
2162 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2163 gboolean autowrap = prefs_common.autowrap;
2164 gboolean autoindent = prefs_common.auto_indent;
2165 HeaderEntry *manual_headers = NULL;
2167 cm_return_val_if_fail(msginfo != NULL, NULL);
2168 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2170 if (compose_put_existing_to_front(msginfo)) {
2174 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2175 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2176 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2177 gchar queueheader_buf[BUFFSIZE];
2180 /* Select Account from queue headers */
2181 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2182 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2183 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2184 account = account_find_from_id(id);
2186 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2187 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2188 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2189 account = account_find_from_id(id);
2191 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2192 sizeof(queueheader_buf), "NAID:")) {
2193 id = atoi(&queueheader_buf[strlen("NAID:")]);
2194 account = account_find_from_id(id);
2196 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2197 sizeof(queueheader_buf), "MAID:")) {
2198 id = atoi(&queueheader_buf[strlen("MAID:")]);
2199 account = account_find_from_id(id);
2201 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2202 sizeof(queueheader_buf), "S:")) {
2203 account = account_find_from_address(queueheader_buf, FALSE);
2205 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2206 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2207 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2208 use_signing = param;
2211 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2212 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2213 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2214 use_signing = param;
2217 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2218 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2219 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2220 use_encryption = param;
2222 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2223 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2224 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2225 use_encryption = param;
2227 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2228 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2229 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2232 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2233 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2234 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2237 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2238 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2239 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2241 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2243 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2245 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2246 sizeof(queueheader_buf), "X-Priority: ")) {
2247 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "RMID:")) {
2252 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2253 if (tokens[0] && tokens[1] && tokens[2]) {
2254 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2255 if (orig_item != NULL) {
2256 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2261 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "FMID:")) {
2263 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2264 if (tokens[0] && tokens[1] && tokens[2]) {
2265 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2266 if (orig_item != NULL) {
2267 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2272 /* Get manual headers */
2273 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2274 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2275 if (*listmh != '\0') {
2276 debug_print("Got manual headers: %s\n", listmh);
2277 manual_headers = procheader_entries_from_str(listmh);
2282 account = msginfo->folder->folder->account;
2285 if (!account && prefs_common.reedit_account_autosel) {
2286 gchar from[BUFFSIZE];
2287 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2288 extract_address(from);
2289 account = account_find_from_address(from, FALSE);
2293 account = cur_account;
2295 cm_return_val_if_fail(account != NULL, NULL);
2297 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2299 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2300 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2301 compose->autowrap = autowrap;
2302 compose->replyinfo = replyinfo;
2303 compose->fwdinfo = fwdinfo;
2305 compose->updating = TRUE;
2306 compose->priority = priority;
2308 if (privacy_system != NULL) {
2309 compose->privacy_system = privacy_system;
2310 compose_use_signing(compose, use_signing);
2311 compose_use_encryption(compose, use_encryption);
2312 compose_update_privacy_system_menu_item(compose, FALSE);
2314 activate_privacy_system(compose, account, FALSE);
2317 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2319 compose_extract_original_charset(compose);
2321 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2322 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2323 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2324 gchar queueheader_buf[BUFFSIZE];
2326 /* Set message save folder */
2327 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2328 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2329 compose_set_save_to(compose, &queueheader_buf[4]);
2331 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2332 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2334 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2339 if (compose_parse_header(compose, msginfo) < 0) {
2340 compose->updating = FALSE;
2341 compose_destroy(compose);
2344 compose_reedit_set_entry(compose, msginfo);
2346 textview = GTK_TEXT_VIEW(compose->text);
2347 textbuf = gtk_text_view_get_buffer(textview);
2348 compose_create_tags(textview, compose);
2350 mark = gtk_text_buffer_get_insert(textbuf);
2351 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2353 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2354 G_CALLBACK(compose_changed_cb),
2357 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2358 fp = procmime_get_first_encrypted_text_content(msginfo);
2360 compose_force_encryption(compose, account, TRUE, NULL);
2363 fp = procmime_get_first_text_content(msginfo);
2366 g_warning("Can't get text part\n");
2370 gboolean prev_autowrap;
2371 GtkTextBuffer *buffer;
2373 while (fgets(buf, sizeof(buf), fp) != NULL) {
2375 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2381 compose_attach_parts(compose, msginfo);
2383 compose_colorize_signature(compose);
2385 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2386 G_CALLBACK(compose_changed_cb),
2389 if (manual_headers != NULL) {
2390 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2391 procheader_entries_free(manual_headers);
2392 compose->updating = FALSE;
2393 compose_destroy(compose);
2396 procheader_entries_free(manual_headers);
2399 gtk_widget_grab_focus(compose->text);
2401 if (prefs_common.auto_exteditor) {
2402 compose_exec_ext_editor(compose);
2404 compose->modified = FALSE;
2405 compose_set_title(compose);
2407 compose->updating = FALSE;
2408 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2409 SCROLL_TO_CURSOR(compose);
2411 if (compose->deferred_destroy) {
2412 compose_destroy(compose);
2416 compose->sig_str = account_get_signature_str(compose->account);
2418 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2423 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2430 cm_return_val_if_fail(msginfo != NULL, NULL);
2433 account = account_get_reply_account(msginfo,
2434 prefs_common.reply_account_autosel);
2435 cm_return_val_if_fail(account != NULL, NULL);
2437 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2439 compose->updating = TRUE;
2441 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2442 compose->replyinfo = NULL;
2443 compose->fwdinfo = NULL;
2445 compose_show_first_last_header(compose, TRUE);
2447 gtk_widget_grab_focus(compose->header_last->entry);
2449 filename = procmsg_get_message_file(msginfo);
2451 if (filename == NULL) {
2452 compose->updating = FALSE;
2453 compose_destroy(compose);
2458 compose->redirect_filename = filename;
2460 /* Set save folder */
2461 item = msginfo->folder;
2462 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2463 gchar *folderidentifier;
2465 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2466 folderidentifier = folder_item_get_identifier(item);
2467 compose_set_save_to(compose, folderidentifier);
2468 g_free(folderidentifier);
2471 compose_attach_parts(compose, msginfo);
2473 if (msginfo->subject)
2474 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2476 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2478 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2479 _("The body of the \"Redirect\" template has an error at line %d."));
2480 quote_fmt_reset_vartable();
2481 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2483 compose_colorize_signature(compose);
2486 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2487 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2488 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2490 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2491 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2492 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2493 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2494 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2496 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2497 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2499 if (compose->toolbar->draft_btn)
2500 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2501 if (compose->toolbar->insert_btn)
2502 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2503 if (compose->toolbar->attach_btn)
2504 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2505 if (compose->toolbar->sig_btn)
2506 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2507 if (compose->toolbar->exteditor_btn)
2508 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2509 if (compose->toolbar->linewrap_current_btn)
2510 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2511 if (compose->toolbar->linewrap_all_btn)
2512 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2514 compose->modified = FALSE;
2515 compose_set_title(compose);
2516 compose->updating = FALSE;
2517 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2518 SCROLL_TO_CURSOR(compose);
2520 if (compose->deferred_destroy) {
2521 compose_destroy(compose);
2525 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2530 GList *compose_get_compose_list(void)
2532 return compose_list;
2535 void compose_entry_append(Compose *compose, const gchar *address,
2536 ComposeEntryType type, ComposePrefType pref_type)
2538 const gchar *header;
2540 gboolean in_quote = FALSE;
2541 if (!address || *address == '\0') return;
2548 header = N_("Bcc:");
2550 case COMPOSE_REPLYTO:
2551 header = N_("Reply-To:");
2553 case COMPOSE_NEWSGROUPS:
2554 header = N_("Newsgroups:");
2556 case COMPOSE_FOLLOWUPTO:
2557 header = N_( "Followup-To:");
2559 case COMPOSE_INREPLYTO:
2560 header = N_( "In-Reply-To:");
2567 header = prefs_common_translated_header_name(header);
2569 cur = begin = (gchar *)address;
2571 /* we separate the line by commas, but not if we're inside a quoted
2573 while (*cur != '\0') {
2575 in_quote = !in_quote;
2576 if (*cur == ',' && !in_quote) {
2577 gchar *tmp = g_strdup(begin);
2579 tmp[cur-begin]='\0';
2582 while (*tmp == ' ' || *tmp == '\t')
2584 compose_add_header_entry(compose, header, tmp, pref_type);
2591 gchar *tmp = g_strdup(begin);
2593 tmp[cur-begin]='\0';
2594 while (*tmp == ' ' || *tmp == '\t')
2596 compose_add_header_entry(compose, header, tmp, pref_type);
2601 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2603 #if !GTK_CHECK_VERSION(3, 0, 0)
2604 static GdkColor yellow;
2605 static GdkColor black;
2606 static gboolean yellow_initialised = FALSE;
2608 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2609 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2614 #if !GTK_CHECK_VERSION(3, 0, 0)
2615 if (!yellow_initialised) {
2616 gdk_color_parse("#f5f6be", &yellow);
2617 gdk_color_parse("#000000", &black);
2618 yellow_initialised = gdk_colormap_alloc_color(
2619 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2620 yellow_initialised &= gdk_colormap_alloc_color(
2621 gdk_colormap_get_system(), &black, FALSE, TRUE);
2625 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2626 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2627 if (gtk_entry_get_text(entry) &&
2628 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2629 #if !GTK_CHECK_VERSION(3, 0, 0)
2630 if (yellow_initialised) {
2632 gtk_widget_modify_base(
2633 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2634 GTK_STATE_NORMAL, &yellow);
2635 gtk_widget_modify_text(
2636 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2637 GTK_STATE_NORMAL, &black);
2638 #if !GTK_CHECK_VERSION(3, 0, 0)
2645 void compose_toolbar_cb(gint action, gpointer data)
2647 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2648 Compose *compose = (Compose*)toolbar_item->parent;
2650 cm_return_if_fail(compose != NULL);
2654 compose_send_cb(NULL, compose);
2657 compose_send_later_cb(NULL, compose);
2660 compose_draft(compose, COMPOSE_QUIT_EDITING);
2663 compose_insert_file_cb(NULL, compose);
2666 compose_attach_cb(NULL, compose);
2669 compose_insert_sig(compose, FALSE);
2672 compose_ext_editor_cb(NULL, compose);
2674 case A_LINEWRAP_CURRENT:
2675 compose_beautify_paragraph(compose, NULL, TRUE);
2677 case A_LINEWRAP_ALL:
2678 compose_wrap_all_full(compose, TRUE);
2681 compose_address_cb(NULL, compose);
2684 case A_CHECK_SPELLING:
2685 compose_check_all(NULL, compose);
2693 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2698 gchar *subject = NULL;
2702 gchar **attach = NULL;
2703 gchar *inreplyto = NULL;
2704 MailField mfield = NO_FIELD_PRESENT;
2706 /* get mailto parts but skip from */
2707 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2710 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2711 mfield = TO_FIELD_PRESENT;
2714 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2716 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2718 if (!g_utf8_validate (subject, -1, NULL)) {
2719 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2720 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2723 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2725 mfield = SUBJECT_FIELD_PRESENT;
2728 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2729 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2732 gboolean prev_autowrap = compose->autowrap;
2734 compose->autowrap = FALSE;
2736 mark = gtk_text_buffer_get_insert(buffer);
2737 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2739 if (!g_utf8_validate (body, -1, NULL)) {
2740 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2741 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2744 gtk_text_buffer_insert(buffer, &iter, body, -1);
2746 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2748 compose->autowrap = prev_autowrap;
2749 if (compose->autowrap)
2750 compose_wrap_all(compose);
2751 mfield = BODY_FIELD_PRESENT;
2755 gint i = 0, att = 0;
2756 gchar *warn_files = NULL;
2757 while (attach[i] != NULL) {
2758 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2759 if (utf8_filename) {
2760 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2761 gchar *tmp = g_strdup_printf("%s%s\n",
2762 warn_files?warn_files:"",
2768 g_free(utf8_filename);
2770 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2775 alertpanel_notice(ngettext(
2776 "The following file has been attached: \n%s",
2777 "The following files have been attached: \n%s", att), warn_files);
2782 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2795 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2797 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2798 {"Cc:", NULL, TRUE},
2799 {"References:", NULL, FALSE},
2800 {"Bcc:", NULL, TRUE},
2801 {"Newsgroups:", NULL, TRUE},
2802 {"Followup-To:", NULL, TRUE},
2803 {"List-Post:", NULL, FALSE},
2804 {"X-Priority:", NULL, FALSE},
2805 {NULL, NULL, FALSE}};
2821 cm_return_val_if_fail(msginfo != NULL, -1);
2823 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2824 procheader_get_header_fields(fp, hentry);
2827 if (hentry[H_REPLY_TO].body != NULL) {
2828 if (hentry[H_REPLY_TO].body[0] != '\0') {
2830 conv_unmime_header(hentry[H_REPLY_TO].body,
2833 g_free(hentry[H_REPLY_TO].body);
2834 hentry[H_REPLY_TO].body = NULL;
2836 if (hentry[H_CC].body != NULL) {
2837 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2838 g_free(hentry[H_CC].body);
2839 hentry[H_CC].body = NULL;
2841 if (hentry[H_REFERENCES].body != NULL) {
2842 if (compose->mode == COMPOSE_REEDIT)
2843 compose->references = hentry[H_REFERENCES].body;
2845 compose->references = compose_parse_references
2846 (hentry[H_REFERENCES].body, msginfo->msgid);
2847 g_free(hentry[H_REFERENCES].body);
2849 hentry[H_REFERENCES].body = NULL;
2851 if (hentry[H_BCC].body != NULL) {
2852 if (compose->mode == COMPOSE_REEDIT)
2854 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2855 g_free(hentry[H_BCC].body);
2856 hentry[H_BCC].body = NULL;
2858 if (hentry[H_NEWSGROUPS].body != NULL) {
2859 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2860 hentry[H_NEWSGROUPS].body = NULL;
2862 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2863 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2864 compose->followup_to =
2865 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2868 g_free(hentry[H_FOLLOWUP_TO].body);
2869 hentry[H_FOLLOWUP_TO].body = NULL;
2871 if (hentry[H_LIST_POST].body != NULL) {
2872 gchar *to = NULL, *start = NULL;
2874 extract_address(hentry[H_LIST_POST].body);
2875 if (hentry[H_LIST_POST].body[0] != '\0') {
2876 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2878 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2879 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2882 g_free(compose->ml_post);
2883 compose->ml_post = to;
2886 g_free(hentry[H_LIST_POST].body);
2887 hentry[H_LIST_POST].body = NULL;
2890 /* CLAWS - X-Priority */
2891 if (compose->mode == COMPOSE_REEDIT)
2892 if (hentry[H_X_PRIORITY].body != NULL) {
2895 priority = atoi(hentry[H_X_PRIORITY].body);
2896 g_free(hentry[H_X_PRIORITY].body);
2898 hentry[H_X_PRIORITY].body = NULL;
2900 if (priority < PRIORITY_HIGHEST ||
2901 priority > PRIORITY_LOWEST)
2902 priority = PRIORITY_NORMAL;
2904 compose->priority = priority;
2907 if (compose->mode == COMPOSE_REEDIT) {
2908 if (msginfo->inreplyto && *msginfo->inreplyto)
2909 compose->inreplyto = g_strdup(msginfo->inreplyto);
2913 if (msginfo->msgid && *msginfo->msgid)
2914 compose->inreplyto = g_strdup(msginfo->msgid);
2916 if (!compose->references) {
2917 if (msginfo->msgid && *msginfo->msgid) {
2918 if (msginfo->inreplyto && *msginfo->inreplyto)
2919 compose->references =
2920 g_strdup_printf("<%s>\n\t<%s>",
2924 compose->references =
2925 g_strconcat("<", msginfo->msgid, ">",
2927 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2928 compose->references =
2929 g_strconcat("<", msginfo->inreplyto, ">",
2937 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2942 cm_return_val_if_fail(msginfo != NULL, -1);
2944 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2945 procheader_get_header_fields(fp, entries);
2949 while (he != NULL && he->name != NULL) {
2951 GtkListStore *model = NULL;
2953 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2954 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2955 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2956 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2957 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2964 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2966 GSList *ref_id_list, *cur;
2970 ref_id_list = references_list_append(NULL, ref);
2971 if (!ref_id_list) return NULL;
2972 if (msgid && *msgid)
2973 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2978 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2979 /* "<" + Message-ID + ">" + CR+LF+TAB */
2980 len += strlen((gchar *)cur->data) + 5;
2982 if (len > MAX_REFERENCES_LEN) {
2983 /* remove second message-ID */
2984 if (ref_id_list && ref_id_list->next &&
2985 ref_id_list->next->next) {
2986 g_free(ref_id_list->next->data);
2987 ref_id_list = g_slist_remove
2988 (ref_id_list, ref_id_list->next->data);
2990 slist_free_strings_full(ref_id_list);
2997 new_ref = g_string_new("");
2998 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2999 if (new_ref->len > 0)
3000 g_string_append(new_ref, "\n\t");
3001 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3004 slist_free_strings_full(ref_id_list);
3006 new_ref_str = new_ref->str;
3007 g_string_free(new_ref, FALSE);
3012 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3013 const gchar *fmt, const gchar *qmark,
3014 const gchar *body, gboolean rewrap,
3015 gboolean need_unescape,
3016 const gchar *err_msg)
3018 MsgInfo* dummyinfo = NULL;
3019 gchar *quote_str = NULL;
3021 gboolean prev_autowrap;
3022 const gchar *trimmed_body = body;
3023 gint cursor_pos = -1;
3024 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3025 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3030 SIGNAL_BLOCK(buffer);
3033 dummyinfo = compose_msginfo_new_from_compose(compose);
3034 msginfo = dummyinfo;
3037 if (qmark != NULL) {
3039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3040 compose->gtkaspell);
3042 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3044 quote_fmt_scan_string(qmark);
3047 buf = quote_fmt_get_buffer();
3049 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3051 Xstrdup_a(quote_str, buf, goto error)
3054 if (fmt && *fmt != '\0') {
3057 while (*trimmed_body == '\n')
3061 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3062 compose->gtkaspell);
3064 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3066 if (need_unescape) {
3069 /* decode \-escape sequences in the internal representation of the quote format */
3070 tmp = g_malloc(strlen(fmt)+1);
3071 pref_get_unescaped_pref(tmp, fmt);
3072 quote_fmt_scan_string(tmp);
3076 quote_fmt_scan_string(fmt);
3080 buf = quote_fmt_get_buffer();
3082 gint line = quote_fmt_get_line();
3083 alertpanel_error(err_msg, line);
3089 prev_autowrap = compose->autowrap;
3090 compose->autowrap = FALSE;
3092 mark = gtk_text_buffer_get_insert(buffer);
3093 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3094 if (g_utf8_validate(buf, -1, NULL)) {
3095 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3097 gchar *tmpout = NULL;
3098 tmpout = conv_codeset_strdup
3099 (buf, conv_get_locale_charset_str_no_utf8(),
3101 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3103 tmpout = g_malloc(strlen(buf)*2+1);
3104 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3106 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3110 cursor_pos = quote_fmt_get_cursor_pos();
3111 if (cursor_pos == -1)
3112 cursor_pos = gtk_text_iter_get_offset(&iter);
3113 compose->set_cursor_pos = cursor_pos;
3115 gtk_text_buffer_get_start_iter(buffer, &iter);
3116 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3117 gtk_text_buffer_place_cursor(buffer, &iter);
3119 compose->autowrap = prev_autowrap;
3120 if (compose->autowrap && rewrap)
3121 compose_wrap_all(compose);
3128 SIGNAL_UNBLOCK(buffer);
3130 procmsg_msginfo_free( dummyinfo );
3135 /* if ml_post is of type addr@host and from is of type
3136 * addr-anything@host, return TRUE
3138 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3140 gchar *left_ml = NULL;
3141 gchar *right_ml = NULL;
3142 gchar *left_from = NULL;
3143 gchar *right_from = NULL;
3144 gboolean result = FALSE;
3146 if (!ml_post || !from)
3149 left_ml = g_strdup(ml_post);
3150 if (strstr(left_ml, "@")) {
3151 right_ml = strstr(left_ml, "@")+1;
3152 *(strstr(left_ml, "@")) = '\0';
3155 left_from = g_strdup(from);
3156 if (strstr(left_from, "@")) {
3157 right_from = strstr(left_from, "@")+1;
3158 *(strstr(left_from, "@")) = '\0';
3161 if (left_ml && left_from && right_ml && right_from
3162 && !strncmp(left_from, left_ml, strlen(left_ml))
3163 && !strcmp(right_from, right_ml)) {
3172 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3173 gboolean respect_default_to)
3177 if (!folder || !folder->prefs)
3180 if (respect_default_to && folder->prefs->enable_default_to) {
3181 compose_entry_append(compose, folder->prefs->default_to,
3182 COMPOSE_TO, PREF_FOLDER);
3183 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3185 if (folder->prefs->enable_default_cc)
3186 compose_entry_append(compose, folder->prefs->default_cc,
3187 COMPOSE_CC, PREF_FOLDER);
3188 if (folder->prefs->enable_default_bcc)
3189 compose_entry_append(compose, folder->prefs->default_bcc,
3190 COMPOSE_BCC, PREF_FOLDER);
3191 if (folder->prefs->enable_default_replyto)
3192 compose_entry_append(compose, folder->prefs->default_replyto,
3193 COMPOSE_REPLYTO, PREF_FOLDER);
3196 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3201 if (!compose || !msginfo)
3204 if (msginfo->subject && *msginfo->subject) {
3205 buf = p = g_strdup(msginfo->subject);
3206 p += subject_get_prefix_length(p);
3207 memmove(buf, p, strlen(p) + 1);
3209 buf2 = g_strdup_printf("Re: %s", buf);
3210 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3215 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3218 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3219 gboolean to_all, gboolean to_ml,
3221 gboolean followup_and_reply_to)
3223 GSList *cc_list = NULL;
3226 gchar *replyto = NULL;
3227 gchar *ac_email = NULL;
3229 gboolean reply_to_ml = FALSE;
3230 gboolean default_reply_to = FALSE;
3232 cm_return_if_fail(compose->account != NULL);
3233 cm_return_if_fail(msginfo != NULL);
3235 reply_to_ml = to_ml && compose->ml_post;
3237 default_reply_to = msginfo->folder &&
3238 msginfo->folder->prefs->enable_default_reply_to;
3240 if (compose->account->protocol != A_NNTP) {
3241 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3243 if (reply_to_ml && !default_reply_to) {
3245 gboolean is_subscr = is_subscription(compose->ml_post,
3248 /* normal answer to ml post with a reply-to */
3249 compose_entry_append(compose,
3251 COMPOSE_TO, PREF_ML);
3252 if (compose->replyto)
3253 compose_entry_append(compose,
3255 COMPOSE_CC, PREF_ML);
3257 /* answer to subscription confirmation */
3258 if (compose->replyto)
3259 compose_entry_append(compose,
3261 COMPOSE_TO, PREF_ML);
3262 else if (msginfo->from)
3263 compose_entry_append(compose,
3265 COMPOSE_TO, PREF_ML);
3268 else if (!(to_all || to_sender) && default_reply_to) {
3269 compose_entry_append(compose,
3270 msginfo->folder->prefs->default_reply_to,
3271 COMPOSE_TO, PREF_FOLDER);
3272 compose_entry_mark_default_to(compose,
3273 msginfo->folder->prefs->default_reply_to);
3278 Xstrdup_a(tmp1, msginfo->from, return);
3279 extract_address(tmp1);
3280 if (to_all || to_sender ||
3281 !account_find_from_address(tmp1, FALSE))
3282 compose_entry_append(compose,
3283 (compose->replyto && !to_sender)
3284 ? compose->replyto :
3285 msginfo->from ? msginfo->from : "",
3286 COMPOSE_TO, PREF_NONE);
3287 else if (!to_all && !to_sender) {
3288 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3289 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3290 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3291 if (compose->replyto) {
3292 compose_entry_append(compose,
3294 COMPOSE_TO, PREF_NONE);
3296 compose_entry_append(compose,
3297 msginfo->from ? msginfo->from : "",
3298 COMPOSE_TO, PREF_NONE);
3301 /* replying to own mail, use original recp */
3302 compose_entry_append(compose,
3303 msginfo->to ? msginfo->to : "",
3304 COMPOSE_TO, PREF_NONE);
3305 compose_entry_append(compose,
3306 msginfo->cc ? msginfo->cc : "",
3307 COMPOSE_CC, PREF_NONE);
3312 if (to_sender || (compose->followup_to &&
3313 !strncmp(compose->followup_to, "poster", 6)))
3314 compose_entry_append
3316 (compose->replyto ? compose->replyto :
3317 msginfo->from ? msginfo->from : ""),
3318 COMPOSE_TO, PREF_NONE);
3320 else if (followup_and_reply_to || to_all) {
3321 compose_entry_append
3323 (compose->replyto ? compose->replyto :
3324 msginfo->from ? msginfo->from : ""),
3325 COMPOSE_TO, PREF_NONE);
3327 compose_entry_append
3329 compose->followup_to ? compose->followup_to :
3330 compose->newsgroups ? compose->newsgroups : "",
3331 COMPOSE_NEWSGROUPS, PREF_NONE);
3334 compose_entry_append
3336 compose->followup_to ? compose->followup_to :
3337 compose->newsgroups ? compose->newsgroups : "",
3338 COMPOSE_NEWSGROUPS, PREF_NONE);
3340 compose_reply_set_subject(compose, msginfo);
3342 if (to_ml && compose->ml_post) return;
3343 if (!to_all || compose->account->protocol == A_NNTP) return;
3345 if (compose->replyto) {
3346 Xstrdup_a(replyto, compose->replyto, return);
3347 extract_address(replyto);
3349 if (msginfo->from) {
3350 Xstrdup_a(from, msginfo->from, return);
3351 extract_address(from);
3354 if (replyto && from)
3355 cc_list = address_list_append_with_comments(cc_list, from);
3356 if (to_all && msginfo->folder &&
3357 msginfo->folder->prefs->enable_default_reply_to)
3358 cc_list = address_list_append_with_comments(cc_list,
3359 msginfo->folder->prefs->default_reply_to);
3360 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3361 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3363 ac_email = g_utf8_strdown(compose->account->address, -1);
3366 for (cur = cc_list; cur != NULL; cur = cur->next) {
3367 gchar *addr = g_utf8_strdown(cur->data, -1);
3368 extract_address(addr);
3370 if (strcmp(ac_email, addr))
3371 compose_entry_append(compose, (gchar *)cur->data,
3372 COMPOSE_CC, PREF_NONE);
3374 debug_print("Cc address same as compose account's, ignoring\n");
3379 slist_free_strings_full(cc_list);
3385 #define SET_ENTRY(entry, str) \
3388 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3391 #define SET_ADDRESS(type, str) \
3394 compose_entry_append(compose, str, type, PREF_NONE); \
3397 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3399 cm_return_if_fail(msginfo != NULL);
3401 SET_ENTRY(subject_entry, msginfo->subject);
3402 SET_ENTRY(from_name, msginfo->from);
3403 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3404 SET_ADDRESS(COMPOSE_CC, compose->cc);
3405 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3406 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3407 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3408 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3410 compose_update_priority_menu_item(compose);
3411 compose_update_privacy_system_menu_item(compose, FALSE);
3412 compose_show_first_last_header(compose, TRUE);
3418 static void compose_insert_sig(Compose *compose, gboolean replace)
3420 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3421 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3423 GtkTextIter iter, iter_end;
3424 gint cur_pos, ins_pos;
3425 gboolean prev_autowrap;
3426 gboolean found = FALSE;
3427 gboolean exists = FALSE;
3429 cm_return_if_fail(compose->account != NULL);
3433 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3434 G_CALLBACK(compose_changed_cb),
3437 mark = gtk_text_buffer_get_insert(buffer);
3438 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3439 cur_pos = gtk_text_iter_get_offset (&iter);
3442 gtk_text_buffer_get_end_iter(buffer, &iter);
3444 exists = (compose->sig_str != NULL);
3447 GtkTextIter first_iter, start_iter, end_iter;
3449 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3451 if (!exists || compose->sig_str[0] == '\0')
3454 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3455 compose->signature_tag);
3458 /* include previous \n\n */
3459 gtk_text_iter_backward_chars(&first_iter, 1);
3460 start_iter = first_iter;
3461 end_iter = first_iter;
3463 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3464 compose->signature_tag);
3465 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3466 compose->signature_tag);
3468 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3474 g_free(compose->sig_str);
3475 compose->sig_str = account_get_signature_str(compose->account);
3477 cur_pos = gtk_text_iter_get_offset(&iter);
3479 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3480 g_free(compose->sig_str);
3481 compose->sig_str = NULL;
3483 if (compose->sig_inserted == FALSE)
3484 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3485 compose->sig_inserted = TRUE;
3487 cur_pos = gtk_text_iter_get_offset(&iter);
3488 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3490 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3491 gtk_text_iter_forward_chars(&iter, 1);
3492 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3493 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3495 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3496 cur_pos = gtk_text_buffer_get_char_count (buffer);
3499 /* put the cursor where it should be
3500 * either where the quote_fmt says, either where it was */
3501 if (compose->set_cursor_pos < 0)
3502 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3504 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3505 compose->set_cursor_pos);
3507 compose->set_cursor_pos = -1;
3508 gtk_text_buffer_place_cursor(buffer, &iter);
3509 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3510 G_CALLBACK(compose_changed_cb),
3516 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3519 GtkTextBuffer *buffer;
3522 const gchar *cur_encoding;
3523 gchar buf[BUFFSIZE];
3526 gboolean prev_autowrap;
3527 gboolean badtxt = FALSE;
3528 struct stat file_stat;
3530 GString *file_contents = NULL;
3532 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3534 /* get the size of the file we are about to insert */
3535 ret = g_stat(file, &file_stat);
3537 gchar *shortfile = g_path_get_basename(file);
3538 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3540 return COMPOSE_INSERT_NO_FILE;
3541 } else if (prefs_common.warn_large_insert == TRUE) {
3543 /* ask user for confirmation if the file is large */
3544 if (prefs_common.warn_large_insert_size < 0 ||
3545 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3549 msg = g_strdup_printf(_("You are about to insert a file of %s "
3550 "in the message body. Are you sure you want to do that?"),
3551 to_human_readable(file_stat.st_size));
3552 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3553 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3556 /* do we ask for confirmation next time? */
3557 if (aval & G_ALERTDISABLE) {
3558 /* no confirmation next time, disable feature in preferences */
3559 aval &= ~G_ALERTDISABLE;
3560 prefs_common.warn_large_insert = FALSE;
3563 /* abort file insertion if user canceled action */
3564 if (aval != G_ALERTALTERNATE) {
3565 return COMPOSE_INSERT_NO_FILE;
3571 if ((fp = g_fopen(file, "rb")) == NULL) {
3572 FILE_OP_ERROR(file, "fopen");
3573 return COMPOSE_INSERT_READ_ERROR;
3576 prev_autowrap = compose->autowrap;
3577 compose->autowrap = FALSE;
3579 text = GTK_TEXT_VIEW(compose->text);
3580 buffer = gtk_text_view_get_buffer(text);
3581 mark = gtk_text_buffer_get_insert(buffer);
3582 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3584 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3585 G_CALLBACK(text_inserted),
3588 cur_encoding = conv_get_locale_charset_str_no_utf8();
3590 file_contents = g_string_new("");
3591 while (fgets(buf, sizeof(buf), fp) != NULL) {
3594 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3595 str = g_strdup(buf);
3597 str = conv_codeset_strdup
3598 (buf, cur_encoding, CS_INTERNAL);
3601 /* strip <CR> if DOS/Windows file,
3602 replace <CR> with <LF> if Macintosh file. */
3605 if (len > 0 && str[len - 1] != '\n') {
3607 if (str[len] == '\r') str[len] = '\n';
3610 file_contents = g_string_append(file_contents, str);
3614 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3615 g_string_free(file_contents, TRUE);
3617 compose_changed_cb(NULL, compose);
3618 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3619 G_CALLBACK(text_inserted),
3621 compose->autowrap = prev_autowrap;
3622 if (compose->autowrap)
3623 compose_wrap_all(compose);
3628 return COMPOSE_INSERT_INVALID_CHARACTER;
3630 return COMPOSE_INSERT_SUCCESS;
3633 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3634 const gchar *filename,
3635 const gchar *content_type,
3636 const gchar *charset)
3644 GtkListStore *store;
3646 gboolean has_binary = FALSE;
3648 if (!is_file_exist(file)) {
3649 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3650 gboolean result = FALSE;
3651 if (file_from_uri && is_file_exist(file_from_uri)) {
3652 result = compose_attach_append(
3653 compose, file_from_uri,
3654 filename, content_type,
3657 g_free(file_from_uri);
3660 alertpanel_error("File %s doesn't exist\n", filename);
3663 if ((size = get_file_size(file)) < 0) {
3664 alertpanel_error("Can't get file size of %s\n", filename);
3668 alertpanel_error(_("File %s is empty."), filename);
3671 if ((fp = g_fopen(file, "rb")) == NULL) {
3672 alertpanel_error(_("Can't read %s."), filename);
3677 ainfo = g_new0(AttachInfo, 1);
3678 auto_ainfo = g_auto_pointer_new_with_free
3679 (ainfo, (GFreeFunc) compose_attach_info_free);
3680 ainfo->file = g_strdup(file);
3683 ainfo->content_type = g_strdup(content_type);
3684 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3686 MsgFlags flags = {0, 0};
3688 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3689 ainfo->encoding = ENC_7BIT;
3691 ainfo->encoding = ENC_8BIT;
3693 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3694 if (msginfo && msginfo->subject)
3695 name = g_strdup(msginfo->subject);
3697 name = g_path_get_basename(filename ? filename : file);
3699 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3701 procmsg_msginfo_free(msginfo);
3703 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3704 ainfo->charset = g_strdup(charset);
3705 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3707 ainfo->encoding = ENC_BASE64;
3709 name = g_path_get_basename(filename ? filename : file);
3710 ainfo->name = g_strdup(name);
3714 ainfo->content_type = procmime_get_mime_type(file);
3715 if (!ainfo->content_type) {
3716 ainfo->content_type =
3717 g_strdup("application/octet-stream");
3718 ainfo->encoding = ENC_BASE64;
3719 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3721 procmime_get_encoding_for_text_file(file, &has_binary);
3723 ainfo->encoding = ENC_BASE64;
3724 name = g_path_get_basename(filename ? filename : file);
3725 ainfo->name = g_strdup(name);
3729 if (ainfo->name != NULL
3730 && !strcmp(ainfo->name, ".")) {
3731 g_free(ainfo->name);
3735 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3736 g_free(ainfo->content_type);
3737 ainfo->content_type = g_strdup("application/octet-stream");
3738 g_free(ainfo->charset);
3739 ainfo->charset = NULL;
3742 ainfo->size = (goffset)size;
3743 size_text = to_human_readable((goffset)size);
3745 store = GTK_LIST_STORE(gtk_tree_view_get_model
3746 (GTK_TREE_VIEW(compose->attach_clist)));
3748 gtk_list_store_append(store, &iter);
3749 gtk_list_store_set(store, &iter,
3750 COL_MIMETYPE, ainfo->content_type,
3751 COL_SIZE, size_text,
3752 COL_NAME, ainfo->name,
3753 COL_CHARSET, ainfo->charset,
3755 COL_AUTODATA, auto_ainfo,
3758 g_auto_pointer_free(auto_ainfo);
3759 compose_attach_update_label(compose);
3763 static void compose_use_signing(Compose *compose, gboolean use_signing)
3765 compose->use_signing = use_signing;
3766 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3769 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3771 compose->use_encryption = use_encryption;
3772 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3775 #define NEXT_PART_NOT_CHILD(info) \
3777 node = info->node; \
3778 while (node->children) \
3779 node = g_node_last_child(node); \
3780 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3783 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3787 MimeInfo *firsttext = NULL;
3788 MimeInfo *encrypted = NULL;
3791 const gchar *partname = NULL;
3793 mimeinfo = procmime_scan_message(msginfo);
3794 if (!mimeinfo) return;
3796 if (mimeinfo->node->children == NULL) {
3797 procmime_mimeinfo_free_all(mimeinfo);
3801 /* find first content part */
3802 child = (MimeInfo *) mimeinfo->node->children->data;
3803 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3804 child = (MimeInfo *)child->node->children->data;
3807 if (child->type == MIMETYPE_TEXT) {
3809 debug_print("First text part found\n");
3810 } else if (compose->mode == COMPOSE_REEDIT &&
3811 child->type == MIMETYPE_APPLICATION &&
3812 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3813 encrypted = (MimeInfo *)child->node->parent->data;
3816 child = (MimeInfo *) mimeinfo->node->children->data;
3817 while (child != NULL) {
3820 if (child == encrypted) {
3821 /* skip this part of tree */
3822 NEXT_PART_NOT_CHILD(child);
3826 if (child->type == MIMETYPE_MULTIPART) {
3827 /* get the actual content */
3828 child = procmime_mimeinfo_next(child);
3832 if (child == firsttext) {
3833 child = procmime_mimeinfo_next(child);
3837 outfile = procmime_get_tmp_file_name(child);
3838 if ((err = procmime_get_part(outfile, child)) < 0)
3839 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3841 gchar *content_type;
3843 content_type = procmime_get_content_type_str(child->type, child->subtype);
3845 /* if we meet a pgp signature, we don't attach it, but
3846 * we force signing. */
3847 if ((strcmp(content_type, "application/pgp-signature") &&
3848 strcmp(content_type, "application/pkcs7-signature") &&
3849 strcmp(content_type, "application/x-pkcs7-signature"))
3850 || compose->mode == COMPOSE_REDIRECT) {
3851 partname = procmime_mimeinfo_get_parameter(child, "filename");
3852 if (partname == NULL)
3853 partname = procmime_mimeinfo_get_parameter(child, "name");
3854 if (partname == NULL)
3856 compose_attach_append(compose, outfile,
3857 partname, content_type,
3858 procmime_mimeinfo_get_parameter(child, "charset"));
3860 compose_force_signing(compose, compose->account, NULL);
3862 g_free(content_type);
3865 NEXT_PART_NOT_CHILD(child);
3867 procmime_mimeinfo_free_all(mimeinfo);
3870 #undef NEXT_PART_NOT_CHILD
3875 WAIT_FOR_INDENT_CHAR,
3876 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3879 /* return indent length, we allow:
3880 indent characters followed by indent characters or spaces/tabs,
3881 alphabets and numbers immediately followed by indent characters,
3882 and the repeating sequences of the above
3883 If quote ends with multiple spaces, only the first one is included. */
3884 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3885 const GtkTextIter *start, gint *len)
3887 GtkTextIter iter = *start;
3891 IndentState state = WAIT_FOR_INDENT_CHAR;
3894 gint alnum_count = 0;
3895 gint space_count = 0;
3898 if (prefs_common.quote_chars == NULL) {
3902 while (!gtk_text_iter_ends_line(&iter)) {
3903 wc = gtk_text_iter_get_char(&iter);
3904 if (g_unichar_iswide(wc))
3906 clen = g_unichar_to_utf8(wc, ch);
3910 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3911 is_space = g_unichar_isspace(wc);
3913 if (state == WAIT_FOR_INDENT_CHAR) {
3914 if (!is_indent && !g_unichar_isalnum(wc))
3917 quote_len += alnum_count + space_count + 1;
3918 alnum_count = space_count = 0;
3919 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3922 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3923 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3927 else if (is_indent) {
3928 quote_len += alnum_count + space_count + 1;
3929 alnum_count = space_count = 0;
3932 state = WAIT_FOR_INDENT_CHAR;
3936 gtk_text_iter_forward_char(&iter);
3939 if (quote_len > 0 && space_count > 0)
3945 if (quote_len > 0) {
3947 gtk_text_iter_forward_chars(&iter, quote_len);
3948 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3954 /* return >0 if the line is itemized */
3955 static int compose_itemized_length(GtkTextBuffer *buffer,
3956 const GtkTextIter *start)
3958 GtkTextIter iter = *start;
3963 if (gtk_text_iter_ends_line(&iter))
3968 wc = gtk_text_iter_get_char(&iter);
3969 if (!g_unichar_isspace(wc))
3971 gtk_text_iter_forward_char(&iter);
3972 if (gtk_text_iter_ends_line(&iter))
3976 clen = g_unichar_to_utf8(wc, ch);
3980 if (!strchr("*-+", ch[0]))
3983 gtk_text_iter_forward_char(&iter);
3984 if (gtk_text_iter_ends_line(&iter))
3986 wc = gtk_text_iter_get_char(&iter);
3987 if (g_unichar_isspace(wc)) {
3993 /* return the string at the start of the itemization */
3994 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3995 const GtkTextIter *start)
3997 GtkTextIter iter = *start;
4000 GString *item_chars = g_string_new("");
4003 if (gtk_text_iter_ends_line(&iter))
4008 wc = gtk_text_iter_get_char(&iter);
4009 if (!g_unichar_isspace(wc))
4011 gtk_text_iter_forward_char(&iter);
4012 if (gtk_text_iter_ends_line(&iter))
4014 g_string_append_unichar(item_chars, wc);
4017 str = item_chars->str;
4018 g_string_free(item_chars, FALSE);
4022 /* return the number of spaces at a line's start */
4023 static int compose_left_offset_length(GtkTextBuffer *buffer,
4024 const GtkTextIter *start)
4026 GtkTextIter iter = *start;
4029 if (gtk_text_iter_ends_line(&iter))
4033 wc = gtk_text_iter_get_char(&iter);
4034 if (!g_unichar_isspace(wc))
4037 gtk_text_iter_forward_char(&iter);
4038 if (gtk_text_iter_ends_line(&iter))
4042 gtk_text_iter_forward_char(&iter);
4043 if (gtk_text_iter_ends_line(&iter))
4048 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4049 const GtkTextIter *start,
4050 GtkTextIter *break_pos,
4054 GtkTextIter iter = *start, line_end = *start;
4055 PangoLogAttr *attrs;
4062 gboolean can_break = FALSE;
4063 gboolean do_break = FALSE;
4064 gboolean was_white = FALSE;
4065 gboolean prev_dont_break = FALSE;
4067 gtk_text_iter_forward_to_line_end(&line_end);
4068 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4069 len = g_utf8_strlen(str, -1);
4073 g_warning("compose_get_line_break_pos: len = 0!\n");
4077 /* g_print("breaking line: %d: %s (len = %d)\n",
4078 gtk_text_iter_get_line(&iter), str, len); */
4080 attrs = g_new(PangoLogAttr, len + 1);
4082 pango_default_break(str, -1, NULL, attrs, len + 1);
4086 /* skip quote and leading spaces */
4087 for (i = 0; *p != '\0' && i < len; i++) {
4090 wc = g_utf8_get_char(p);
4091 if (i >= quote_len && !g_unichar_isspace(wc))
4093 if (g_unichar_iswide(wc))
4095 else if (*p == '\t')
4099 p = g_utf8_next_char(p);
4102 for (; *p != '\0' && i < len; i++) {
4103 PangoLogAttr *attr = attrs + i;
4107 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4110 was_white = attr->is_white;
4112 /* don't wrap URI */
4113 if ((uri_len = get_uri_len(p)) > 0) {
4115 if (pos > 0 && col > max_col) {
4125 wc = g_utf8_get_char(p);
4126 if (g_unichar_iswide(wc)) {
4128 if (prev_dont_break && can_break && attr->is_line_break)
4130 } else if (*p == '\t')
4134 if (pos > 0 && col > max_col) {
4139 if (*p == '-' || *p == '/')
4140 prev_dont_break = TRUE;
4142 prev_dont_break = FALSE;
4144 p = g_utf8_next_char(p);
4148 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4153 *break_pos = *start;
4154 gtk_text_iter_set_line_offset(break_pos, pos);
4159 static gboolean compose_join_next_line(Compose *compose,
4160 GtkTextBuffer *buffer,
4162 const gchar *quote_str)
4164 GtkTextIter iter_ = *iter, cur, prev, next, end;
4165 PangoLogAttr attrs[3];
4167 gchar *next_quote_str;
4170 gboolean keep_cursor = FALSE;
4172 if (!gtk_text_iter_forward_line(&iter_) ||
4173 gtk_text_iter_ends_line(&iter_)) {
4176 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4178 if ((quote_str || next_quote_str) &&
4179 strcmp2(quote_str, next_quote_str) != 0) {
4180 g_free(next_quote_str);
4183 g_free(next_quote_str);
4186 if (quote_len > 0) {
4187 gtk_text_iter_forward_chars(&end, quote_len);
4188 if (gtk_text_iter_ends_line(&end)) {
4193 /* don't join itemized lines */
4194 if (compose_itemized_length(buffer, &end) > 0) {
4198 /* don't join signature separator */
4199 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4202 /* delete quote str */
4204 gtk_text_buffer_delete(buffer, &iter_, &end);
4206 /* don't join line breaks put by the user */
4208 gtk_text_iter_backward_char(&cur);
4209 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4210 gtk_text_iter_forward_char(&cur);
4214 gtk_text_iter_forward_char(&cur);
4215 /* delete linebreak and extra spaces */
4216 while (gtk_text_iter_backward_char(&cur)) {
4217 wc1 = gtk_text_iter_get_char(&cur);
4218 if (!g_unichar_isspace(wc1))
4223 while (!gtk_text_iter_ends_line(&cur)) {
4224 wc1 = gtk_text_iter_get_char(&cur);
4225 if (!g_unichar_isspace(wc1))
4227 gtk_text_iter_forward_char(&cur);
4230 if (!gtk_text_iter_equal(&prev, &next)) {
4233 mark = gtk_text_buffer_get_insert(buffer);
4234 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4235 if (gtk_text_iter_equal(&prev, &cur))
4237 gtk_text_buffer_delete(buffer, &prev, &next);
4241 /* insert space if required */
4242 gtk_text_iter_backward_char(&prev);
4243 wc1 = gtk_text_iter_get_char(&prev);
4244 wc2 = gtk_text_iter_get_char(&next);
4245 gtk_text_iter_forward_char(&next);
4246 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4247 pango_default_break(str, -1, NULL, attrs, 3);
4248 if (!attrs[1].is_line_break ||
4249 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4250 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4252 gtk_text_iter_backward_char(&iter_);
4253 gtk_text_buffer_place_cursor(buffer, &iter_);
4262 #define ADD_TXT_POS(bp_, ep_, pti_) \
4263 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4264 last = last->next; \
4265 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4266 last->next = NULL; \
4268 g_warning("alloc error scanning URIs\n"); \
4271 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4273 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4274 GtkTextBuffer *buffer;
4275 GtkTextIter iter, break_pos, end_of_line;
4276 gchar *quote_str = NULL;
4278 gboolean wrap_quote = prefs_common.linewrap_quote;
4279 gboolean prev_autowrap = compose->autowrap;
4280 gint startq_offset = -1, noq_offset = -1;
4281 gint uri_start = -1, uri_stop = -1;
4282 gint nouri_start = -1, nouri_stop = -1;
4283 gint num_blocks = 0;
4284 gint quotelevel = -1;
4285 gboolean modified = force;
4286 gboolean removed = FALSE;
4287 gboolean modified_before_remove = FALSE;
4289 gboolean start = TRUE;
4290 gint itemized_len = 0, rem_item_len = 0;
4291 gchar *itemized_chars = NULL;
4292 gboolean item_continuation = FALSE;
4297 if (compose->draft_timeout_tag == -2) {
4301 compose->autowrap = FALSE;
4303 buffer = gtk_text_view_get_buffer(text);
4304 undo_wrapping(compose->undostruct, TRUE);
4309 mark = gtk_text_buffer_get_insert(buffer);
4310 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4314 if (compose->draft_timeout_tag == -2) {
4315 if (gtk_text_iter_ends_line(&iter)) {
4316 while (gtk_text_iter_ends_line(&iter) &&
4317 gtk_text_iter_forward_line(&iter))
4320 while (gtk_text_iter_backward_line(&iter)) {
4321 if (gtk_text_iter_ends_line(&iter)) {
4322 gtk_text_iter_forward_line(&iter);
4328 /* move to line start */
4329 gtk_text_iter_set_line_offset(&iter, 0);
4332 itemized_len = compose_itemized_length(buffer, &iter);
4334 if (!itemized_len) {
4335 itemized_len = compose_left_offset_length(buffer, &iter);
4336 item_continuation = TRUE;
4340 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4342 /* go until paragraph end (empty line) */
4343 while (start || !gtk_text_iter_ends_line(&iter)) {
4344 gchar *scanpos = NULL;
4345 /* parse table - in order of priority */
4347 const gchar *needle; /* token */
4349 /* token search function */
4350 gchar *(*search) (const gchar *haystack,
4351 const gchar *needle);
4352 /* part parsing function */
4353 gboolean (*parse) (const gchar *start,
4354 const gchar *scanpos,
4358 /* part to URI function */
4359 gchar *(*build_uri) (const gchar *bp,
4363 static struct table parser[] = {
4364 {"http://", strcasestr, get_uri_part, make_uri_string},
4365 {"https://", strcasestr, get_uri_part, make_uri_string},
4366 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4367 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4368 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4369 {"www.", strcasestr, get_uri_part, make_http_string},
4370 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4371 {"@", strcasestr, get_email_part, make_email_string}
4373 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4374 gint last_index = PARSE_ELEMS;
4376 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4380 if (!prev_autowrap && num_blocks == 0) {
4382 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4383 G_CALLBACK(text_inserted),
4386 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4389 uri_start = uri_stop = -1;
4391 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4394 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4395 if (startq_offset == -1)
4396 startq_offset = gtk_text_iter_get_offset(&iter);
4397 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4398 if (quotelevel > 2) {
4399 /* recycle colors */
4400 if (prefs_common.recycle_quote_colors)
4409 if (startq_offset == -1)
4410 noq_offset = gtk_text_iter_get_offset(&iter);
4414 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4417 if (gtk_text_iter_ends_line(&iter)) {
4419 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4420 prefs_common.linewrap_len,
4422 GtkTextIter prev, next, cur;
4423 if (prev_autowrap != FALSE || force) {
4424 compose->automatic_break = TRUE;
4426 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4427 compose->automatic_break = FALSE;
4428 if (itemized_len && compose->autoindent) {
4429 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4430 if (!item_continuation)
4431 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4433 } else if (quote_str && wrap_quote) {
4434 compose->automatic_break = TRUE;
4436 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4437 compose->automatic_break = FALSE;
4438 if (itemized_len && compose->autoindent) {
4439 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4440 if (!item_continuation)
4441 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4445 /* remove trailing spaces */
4447 rem_item_len = itemized_len;
4448 while (compose->autoindent && rem_item_len-- > 0)
4449 gtk_text_iter_backward_char(&cur);
4450 gtk_text_iter_backward_char(&cur);
4453 while (!gtk_text_iter_starts_line(&cur)) {
4456 gtk_text_iter_backward_char(&cur);
4457 wc = gtk_text_iter_get_char(&cur);
4458 if (!g_unichar_isspace(wc))
4462 if (!gtk_text_iter_equal(&prev, &next)) {
4463 gtk_text_buffer_delete(buffer, &prev, &next);
4465 gtk_text_iter_forward_char(&break_pos);
4469 gtk_text_buffer_insert(buffer, &break_pos,
4473 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4475 /* move iter to current line start */
4476 gtk_text_iter_set_line_offset(&iter, 0);
4483 /* move iter to next line start */
4489 if (!prev_autowrap && num_blocks > 0) {
4491 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4492 G_CALLBACK(text_inserted),
4496 while (!gtk_text_iter_ends_line(&end_of_line)) {
4497 gtk_text_iter_forward_char(&end_of_line);
4499 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4501 nouri_start = gtk_text_iter_get_offset(&iter);
4502 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4504 walk_pos = gtk_text_iter_get_offset(&iter);
4505 /* FIXME: this looks phony. scanning for anything in the parse table */
4506 for (n = 0; n < PARSE_ELEMS; n++) {
4509 tmp = parser[n].search(walk, parser[n].needle);
4511 if (scanpos == NULL || tmp < scanpos) {
4520 /* check if URI can be parsed */
4521 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4522 (const gchar **)&ep, FALSE)
4523 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4527 strlen(parser[last_index].needle);
4530 uri_start = walk_pos + (bp - o_walk);
4531 uri_stop = walk_pos + (ep - o_walk);
4535 gtk_text_iter_forward_line(&iter);
4538 if (startq_offset != -1) {
4539 GtkTextIter startquote, endquote;
4540 gtk_text_buffer_get_iter_at_offset(
4541 buffer, &startquote, startq_offset);
4544 switch (quotelevel) {
4546 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4547 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4548 gtk_text_buffer_apply_tag_by_name(
4549 buffer, "quote0", &startquote, &endquote);
4550 gtk_text_buffer_remove_tag_by_name(
4551 buffer, "quote1", &startquote, &endquote);
4552 gtk_text_buffer_remove_tag_by_name(
4553 buffer, "quote2", &startquote, &endquote);
4558 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4559 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4560 gtk_text_buffer_apply_tag_by_name(
4561 buffer, "quote1", &startquote, &endquote);
4562 gtk_text_buffer_remove_tag_by_name(
4563 buffer, "quote0", &startquote, &endquote);
4564 gtk_text_buffer_remove_tag_by_name(
4565 buffer, "quote2", &startquote, &endquote);
4570 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4571 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4572 gtk_text_buffer_apply_tag_by_name(
4573 buffer, "quote2", &startquote, &endquote);
4574 gtk_text_buffer_remove_tag_by_name(
4575 buffer, "quote0", &startquote, &endquote);
4576 gtk_text_buffer_remove_tag_by_name(
4577 buffer, "quote1", &startquote, &endquote);
4583 } else if (noq_offset != -1) {
4584 GtkTextIter startnoquote, endnoquote;
4585 gtk_text_buffer_get_iter_at_offset(
4586 buffer, &startnoquote, noq_offset);
4589 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4590 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4591 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4592 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4593 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4594 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4595 gtk_text_buffer_remove_tag_by_name(
4596 buffer, "quote0", &startnoquote, &endnoquote);
4597 gtk_text_buffer_remove_tag_by_name(
4598 buffer, "quote1", &startnoquote, &endnoquote);
4599 gtk_text_buffer_remove_tag_by_name(
4600 buffer, "quote2", &startnoquote, &endnoquote);
4606 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4607 GtkTextIter nouri_start_iter, nouri_end_iter;
4608 gtk_text_buffer_get_iter_at_offset(
4609 buffer, &nouri_start_iter, nouri_start);
4610 gtk_text_buffer_get_iter_at_offset(
4611 buffer, &nouri_end_iter, nouri_stop);
4612 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4613 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4614 gtk_text_buffer_remove_tag_by_name(
4615 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4616 modified_before_remove = modified;
4621 if (uri_start >= 0 && uri_stop > 0) {
4622 GtkTextIter uri_start_iter, uri_end_iter, back;
4623 gtk_text_buffer_get_iter_at_offset(
4624 buffer, &uri_start_iter, uri_start);
4625 gtk_text_buffer_get_iter_at_offset(
4626 buffer, &uri_end_iter, uri_stop);
4627 back = uri_end_iter;
4628 gtk_text_iter_backward_char(&back);
4629 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4630 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4631 gtk_text_buffer_apply_tag_by_name(
4632 buffer, "link", &uri_start_iter, &uri_end_iter);
4634 if (removed && !modified_before_remove) {
4640 // debug_print("not modified, out after %d lines\n", lines);
4644 // debug_print("modified, out after %d lines\n", lines);
4646 g_free(itemized_chars);
4649 undo_wrapping(compose->undostruct, FALSE);
4650 compose->autowrap = prev_autowrap;
4655 void compose_action_cb(void *data)
4657 Compose *compose = (Compose *)data;
4658 compose_wrap_all(compose);
4661 static void compose_wrap_all(Compose *compose)
4663 compose_wrap_all_full(compose, FALSE);
4666 static void compose_wrap_all_full(Compose *compose, gboolean force)
4668 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4669 GtkTextBuffer *buffer;
4671 gboolean modified = TRUE;
4673 buffer = gtk_text_view_get_buffer(text);
4675 gtk_text_buffer_get_start_iter(buffer, &iter);
4676 while (!gtk_text_iter_is_end(&iter) && modified)
4677 modified = compose_beautify_paragraph(compose, &iter, force);
4681 static void compose_set_title(Compose *compose)
4687 edited = compose->modified ? _(" [Edited]") : "";
4689 subject = gtk_editable_get_chars(
4690 GTK_EDITABLE(compose->subject_entry), 0, -1);
4692 #ifndef GENERIC_UMPC
4693 if (subject && strlen(subject))
4694 str = g_strdup_printf(_("%s - Compose message%s"),
4697 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4699 str = g_strdup(_("Compose message"));
4702 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4708 * compose_current_mail_account:
4710 * Find a current mail account (the currently selected account, or the
4711 * default account, if a news account is currently selected). If a
4712 * mail account cannot be found, display an error message.
4714 * Return value: Mail account, or NULL if not found.
4716 static PrefsAccount *
4717 compose_current_mail_account(void)
4721 if (cur_account && cur_account->protocol != A_NNTP)
4724 ac = account_get_default();
4725 if (!ac || ac->protocol == A_NNTP) {
4726 alertpanel_error(_("Account for sending mail is not specified.\n"
4727 "Please select a mail account before sending."));
4734 #define QUOTE_IF_REQUIRED(out, str) \
4736 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4740 len = strlen(str) + 3; \
4741 if ((__tmp = alloca(len)) == NULL) { \
4742 g_warning("can't allocate memory\n"); \
4743 g_string_free(header, TRUE); \
4746 g_snprintf(__tmp, len, "\"%s\"", str); \
4751 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4752 g_warning("can't allocate memory\n"); \
4753 g_string_free(header, TRUE); \
4756 strcpy(__tmp, str); \
4762 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4764 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4768 len = strlen(str) + 3; \
4769 if ((__tmp = alloca(len)) == NULL) { \
4770 g_warning("can't allocate memory\n"); \
4773 g_snprintf(__tmp, len, "\"%s\"", str); \
4778 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4779 g_warning("can't allocate memory\n"); \
4782 strcpy(__tmp, str); \
4788 static void compose_select_account(Compose *compose, PrefsAccount *account,
4791 gchar *from = NULL, *header = NULL;
4792 ComposeHeaderEntry *header_entry;
4793 #if GTK_CHECK_VERSION(2, 24, 0)
4797 cm_return_if_fail(account != NULL);
4799 compose->account = account;
4800 if (account->name && *account->name) {
4802 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4803 from = g_strdup_printf("%s <%s>",
4804 buf, account->address);
4805 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4807 from = g_strdup_printf("<%s>",
4809 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4814 compose_set_title(compose);
4816 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4817 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4819 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4820 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4821 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4823 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4825 activate_privacy_system(compose, account, FALSE);
4827 if (!init && compose->mode != COMPOSE_REDIRECT) {
4828 undo_block(compose->undostruct);
4829 compose_insert_sig(compose, TRUE);
4830 undo_unblock(compose->undostruct);
4833 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4834 #if !GTK_CHECK_VERSION(2, 24, 0)
4835 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4837 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4838 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4839 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4842 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4843 if (account->protocol == A_NNTP) {
4844 if (!strcmp(header, _("To:")))
4845 combobox_select_by_text(
4846 GTK_COMBO_BOX(header_entry->combo),
4849 if (!strcmp(header, _("Newsgroups:")))
4850 combobox_select_by_text(
4851 GTK_COMBO_BOX(header_entry->combo),
4859 /* use account's dict info if set */
4860 if (compose->gtkaspell) {
4861 if (account->enable_default_dictionary)
4862 gtkaspell_change_dict(compose->gtkaspell,
4863 account->default_dictionary, FALSE);
4864 if (account->enable_default_alt_dictionary)
4865 gtkaspell_change_alt_dict(compose->gtkaspell,
4866 account->default_alt_dictionary);
4867 if (account->enable_default_dictionary
4868 || account->enable_default_alt_dictionary)
4869 compose_spell_menu_changed(compose);
4874 gboolean compose_check_for_valid_recipient(Compose *compose) {
4875 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4876 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4877 gboolean recipient_found = FALSE;
4881 /* free to and newsgroup list */
4882 slist_free_strings_full(compose->to_list);
4883 compose->to_list = NULL;
4885 slist_free_strings_full(compose->newsgroup_list);
4886 compose->newsgroup_list = NULL;
4888 /* search header entries for to and newsgroup entries */
4889 for (list = compose->header_list; list; list = list->next) {
4892 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4893 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4896 if (entry[0] != '\0') {
4897 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4898 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4899 compose->to_list = address_list_append(compose->to_list, entry);
4900 recipient_found = TRUE;
4903 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4904 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4905 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4906 recipient_found = TRUE;
4913 return recipient_found;
4916 static gboolean compose_check_for_set_recipients(Compose *compose)
4918 if (compose->account->set_autocc && compose->account->auto_cc) {
4919 gboolean found_other = FALSE;
4921 /* search header entries for to and newsgroup entries */
4922 for (list = compose->header_list; list; list = list->next) {
4925 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4926 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4929 if (strcmp(entry, compose->account->auto_cc)
4930 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4940 if (compose->batch) {
4941 gtk_widget_show_all(compose->window);
4943 aval = alertpanel(_("Send"),
4944 _("The only recipient is the default CC address. Send anyway?"),
4945 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4946 if (aval != G_ALERTALTERNATE)
4950 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4951 gboolean found_other = FALSE;
4953 /* search header entries for to and newsgroup entries */
4954 for (list = compose->header_list; list; list = list->next) {
4957 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4958 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4961 if (strcmp(entry, compose->account->auto_bcc)
4962 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4972 if (compose->batch) {
4973 gtk_widget_show_all(compose->window);
4975 aval = alertpanel(_("Send"),
4976 _("The only recipient is the default BCC address. Send anyway?"),
4977 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4978 if (aval != G_ALERTALTERNATE)
4985 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4989 if (compose_check_for_valid_recipient(compose) == FALSE) {
4990 if (compose->batch) {
4991 gtk_widget_show_all(compose->window);
4993 alertpanel_error(_("Recipient is not specified."));
4997 if (compose_check_for_set_recipients(compose) == FALSE) {
5001 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5002 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5003 if (*str == '\0' && check_everything == TRUE &&
5004 compose->mode != COMPOSE_REDIRECT) {
5006 gchar *button_label;
5009 if (compose->sending)
5010 button_label = _("+_Send");
5012 button_label = _("+_Queue");
5013 message = g_strdup_printf(_("Subject is empty. %s"),
5014 compose->sending?_("Send it anyway?"):
5015 _("Queue it anyway?"));
5017 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5018 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5019 ALERT_QUESTION, G_ALERTDEFAULT);
5021 if (aval & G_ALERTDISABLE) {
5022 aval &= ~G_ALERTDISABLE;
5023 prefs_common.warn_empty_subj = FALSE;
5025 if (aval != G_ALERTALTERNATE)
5030 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5036 gint compose_send(Compose *compose)
5039 FolderItem *folder = NULL;
5041 gchar *msgpath = NULL;
5042 gboolean discard_window = FALSE;
5043 gchar *errstr = NULL;
5044 gchar *tmsgid = NULL;
5045 MainWindow *mainwin = mainwindow_get_mainwindow();
5046 gboolean queued_removed = FALSE;
5048 if (prefs_common.send_dialog_invisible
5049 || compose->batch == TRUE)
5050 discard_window = TRUE;
5052 compose_allow_user_actions (compose, FALSE);
5053 compose->sending = TRUE;
5055 if (compose_check_entries(compose, TRUE) == FALSE) {
5056 if (compose->batch) {
5057 gtk_widget_show_all(compose->window);
5063 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5066 if (compose->batch) {
5067 gtk_widget_show_all(compose->window);
5070 alertpanel_error(_("Could not queue message for sending:\n\n"
5071 "Charset conversion failed."));
5072 } else if (val == -5) {
5073 alertpanel_error(_("Could not queue message for sending:\n\n"
5074 "Couldn't get recipient encryption key."));
5075 } else if (val == -6) {
5077 } else if (val == -3) {
5078 if (privacy_peek_error())
5079 alertpanel_error(_("Could not queue message for sending:\n\n"
5080 "Signature failed: %s"), privacy_get_error());
5081 } else if (val == -2 && errno != 0) {
5082 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5084 alertpanel_error(_("Could not queue message for sending."));
5089 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5090 if (discard_window) {
5091 compose->sending = FALSE;
5092 compose_close(compose);
5093 /* No more compose access in the normal codepath
5094 * after this point! */
5099 alertpanel_error(_("The message was queued but could not be "
5100 "sent.\nUse \"Send queued messages\" from "
5101 "the main window to retry."));
5102 if (!discard_window) {
5109 if (msgpath == NULL) {
5110 msgpath = folder_item_fetch_msg(folder, msgnum);
5111 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5114 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5115 claws_unlink(msgpath);
5118 if (!discard_window) {
5120 if (!queued_removed)
5121 folder_item_remove_msg(folder, msgnum);
5122 folder_item_scan(folder);
5124 /* make sure we delete that */
5125 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5127 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5128 folder_item_remove_msg(folder, tmp->msgnum);
5129 procmsg_msginfo_free(tmp);
5136 if (!queued_removed)
5137 folder_item_remove_msg(folder, msgnum);
5138 folder_item_scan(folder);
5140 /* make sure we delete that */
5141 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5143 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5144 folder_item_remove_msg(folder, tmp->msgnum);
5145 procmsg_msginfo_free(tmp);
5148 if (!discard_window) {
5149 compose->sending = FALSE;
5150 compose_allow_user_actions (compose, TRUE);
5151 compose_close(compose);
5155 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5156 "the main window to retry."), errstr);
5159 alertpanel_error_log(_("The message was queued but could not be "
5160 "sent.\nUse \"Send queued messages\" from "
5161 "the main window to retry."));
5163 if (!discard_window) {
5172 toolbar_main_set_sensitive(mainwin);
5173 main_window_set_menu_sensitive(mainwin);
5179 compose_allow_user_actions (compose, TRUE);
5180 compose->sending = FALSE;
5181 compose->modified = TRUE;
5182 toolbar_main_set_sensitive(mainwin);
5183 main_window_set_menu_sensitive(mainwin);
5188 static gboolean compose_use_attach(Compose *compose)
5190 GtkTreeModel *model = gtk_tree_view_get_model
5191 (GTK_TREE_VIEW(compose->attach_clist));
5192 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5195 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5198 gchar buf[BUFFSIZE];
5200 gboolean first_to_address;
5201 gboolean first_cc_address;
5203 ComposeHeaderEntry *headerentry;
5204 const gchar *headerentryname;
5205 const gchar *cc_hdr;
5206 const gchar *to_hdr;
5207 gboolean err = FALSE;
5209 debug_print("Writing redirect header\n");
5211 cc_hdr = prefs_common_translated_header_name("Cc:");
5212 to_hdr = prefs_common_translated_header_name("To:");
5214 first_to_address = TRUE;
5215 for (list = compose->header_list; list; list = list->next) {
5216 headerentry = ((ComposeHeaderEntry *)list->data);
5217 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5219 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5220 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5221 Xstrdup_a(str, entstr, return -1);
5223 if (str[0] != '\0') {
5224 compose_convert_header
5225 (compose, buf, sizeof(buf), str,
5226 strlen("Resent-To") + 2, TRUE);
5228 if (first_to_address) {
5229 err |= (fprintf(fp, "Resent-To: ") < 0);
5230 first_to_address = FALSE;
5232 err |= (fprintf(fp, ",") < 0);
5234 err |= (fprintf(fp, "%s", buf) < 0);
5238 if (!first_to_address) {
5239 err |= (fprintf(fp, "\n") < 0);
5242 first_cc_address = TRUE;
5243 for (list = compose->header_list; list; list = list->next) {
5244 headerentry = ((ComposeHeaderEntry *)list->data);
5245 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5247 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5248 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5249 Xstrdup_a(str, strg, return -1);
5251 if (str[0] != '\0') {
5252 compose_convert_header
5253 (compose, buf, sizeof(buf), str,
5254 strlen("Resent-Cc") + 2, TRUE);
5256 if (first_cc_address) {
5257 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5258 first_cc_address = FALSE;
5260 err |= (fprintf(fp, ",") < 0);
5262 err |= (fprintf(fp, "%s", buf) < 0);
5266 if (!first_cc_address) {
5267 err |= (fprintf(fp, "\n") < 0);
5270 return (err ? -1:0);
5273 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5275 gchar buf[BUFFSIZE];
5277 const gchar *entstr;
5278 /* struct utsname utsbuf; */
5279 gboolean err = FALSE;
5281 cm_return_val_if_fail(fp != NULL, -1);
5282 cm_return_val_if_fail(compose->account != NULL, -1);
5283 cm_return_val_if_fail(compose->account->address != NULL, -1);
5286 get_rfc822_date(buf, sizeof(buf));
5287 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5290 if (compose->account->name && *compose->account->name) {
5291 compose_convert_header
5292 (compose, buf, sizeof(buf), compose->account->name,
5293 strlen("From: "), TRUE);
5294 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5295 buf, compose->account->address) < 0);
5297 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5300 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5301 if (*entstr != '\0') {
5302 Xstrdup_a(str, entstr, return -1);
5305 compose_convert_header(compose, buf, sizeof(buf), str,
5306 strlen("Subject: "), FALSE);
5307 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5311 /* Resent-Message-ID */
5312 if (compose->account->set_domain && compose->account->domain) {
5313 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5314 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5315 g_snprintf(buf, sizeof(buf), "%s",
5316 strchr(compose->account->address, '@') ?
5317 strchr(compose->account->address, '@')+1 :
5318 compose->account->address);
5320 g_snprintf(buf, sizeof(buf), "%s", "");
5323 if (compose->account->gen_msgid) {
5325 if (compose->account->msgid_with_addr) {
5326 addr = compose->account->address;
5328 generate_msgid(buf, sizeof(buf), addr);
5329 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5330 compose->msgid = g_strdup(buf);
5332 compose->msgid = NULL;
5335 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5338 /* separator between header and body */
5339 err |= (fputs("\n", fp) == EOF);
5341 return (err ? -1:0);
5344 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5348 gchar buf[BUFFSIZE];
5350 gboolean skip = FALSE;
5351 gboolean err = FALSE;
5352 gchar *not_included[]={
5353 "Return-Path:", "Delivered-To:", "Received:",
5354 "Subject:", "X-UIDL:", "AF:",
5355 "NF:", "PS:", "SRH:",
5356 "SFN:", "DSR:", "MID:",
5357 "CFG:", "PT:", "S:",
5358 "RQ:", "SSV:", "NSV:",
5359 "SSH:", "R:", "MAID:",
5360 "NAID:", "RMID:", "FMID:",
5361 "SCF:", "RRCPT:", "NG:",
5362 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5363 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5364 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5365 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5366 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5369 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5370 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5374 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5376 for (i = 0; not_included[i] != NULL; i++) {
5377 if (g_ascii_strncasecmp(buf, not_included[i],
5378 strlen(not_included[i])) == 0) {
5385 if (fputs(buf, fdest) == -1)
5388 if (!prefs_common.redirect_keep_from) {
5389 if (g_ascii_strncasecmp(buf, "From:",
5390 strlen("From:")) == 0) {
5391 err |= (fputs(" (by way of ", fdest) == EOF);
5392 if (compose->account->name
5393 && *compose->account->name) {
5394 compose_convert_header
5395 (compose, buf, sizeof(buf),
5396 compose->account->name,
5399 err |= (fprintf(fdest, "%s <%s>",
5401 compose->account->address) < 0);
5403 err |= (fprintf(fdest, "%s",
5404 compose->account->address) < 0);
5405 err |= (fputs(")", fdest) == EOF);
5409 if (fputs("\n", fdest) == -1)
5416 if (compose_redirect_write_headers(compose, fdest))
5419 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5420 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5433 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5435 GtkTextBuffer *buffer;
5436 GtkTextIter start, end;
5439 const gchar *out_codeset;
5440 EncodingType encoding = ENC_UNKNOWN;
5441 MimeInfo *mimemsg, *mimetext;
5443 const gchar *src_codeset = CS_INTERNAL;
5444 gchar *from_addr = NULL;
5445 gchar *from_name = NULL;
5447 if (action == COMPOSE_WRITE_FOR_SEND)
5448 attach_parts = TRUE;
5450 /* create message MimeInfo */
5451 mimemsg = procmime_mimeinfo_new();
5452 mimemsg->type = MIMETYPE_MESSAGE;
5453 mimemsg->subtype = g_strdup("rfc822");
5454 mimemsg->content = MIMECONTENT_MEM;
5455 mimemsg->tmp = TRUE; /* must free content later */
5456 mimemsg->data.mem = compose_get_header(compose);
5458 /* Create text part MimeInfo */
5459 /* get all composed text */
5460 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5461 gtk_text_buffer_get_start_iter(buffer, &start);
5462 gtk_text_buffer_get_end_iter(buffer, &end);
5463 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5465 out_codeset = conv_get_charset_str(compose->out_encoding);
5467 if (!out_codeset && is_ascii_str(chars)) {
5468 out_codeset = CS_US_ASCII;
5469 } else if (prefs_common.outgoing_fallback_to_ascii &&
5470 is_ascii_str(chars)) {
5471 out_codeset = CS_US_ASCII;
5472 encoding = ENC_7BIT;
5476 gchar *test_conv_global_out = NULL;
5477 gchar *test_conv_reply = NULL;
5479 /* automatic mode. be automatic. */
5480 codeconv_set_strict(TRUE);
5482 out_codeset = conv_get_outgoing_charset_str();
5484 debug_print("trying to convert to %s\n", out_codeset);
5485 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5488 if (!test_conv_global_out && compose->orig_charset
5489 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5490 out_codeset = compose->orig_charset;
5491 debug_print("failure; trying to convert to %s\n", out_codeset);
5492 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5495 if (!test_conv_global_out && !test_conv_reply) {
5497 out_codeset = CS_INTERNAL;
5498 debug_print("failure; finally using %s\n", out_codeset);
5500 g_free(test_conv_global_out);
5501 g_free(test_conv_reply);
5502 codeconv_set_strict(FALSE);
5505 if (encoding == ENC_UNKNOWN) {
5506 if (prefs_common.encoding_method == CTE_BASE64)
5507 encoding = ENC_BASE64;
5508 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5509 encoding = ENC_QUOTED_PRINTABLE;
5510 else if (prefs_common.encoding_method == CTE_8BIT)
5511 encoding = ENC_8BIT;
5513 encoding = procmime_get_encoding_for_charset(out_codeset);
5516 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5517 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5519 if (action == COMPOSE_WRITE_FOR_SEND) {
5520 codeconv_set_strict(TRUE);
5521 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5522 codeconv_set_strict(FALSE);
5528 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5529 "to the specified %s charset.\n"
5530 "Send it as %s?"), out_codeset, src_codeset);
5531 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5532 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5535 if (aval != G_ALERTALTERNATE) {
5540 out_codeset = src_codeset;
5546 out_codeset = src_codeset;
5551 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5552 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5553 strstr(buf, "\nFrom ") != NULL) {
5554 encoding = ENC_QUOTED_PRINTABLE;
5558 mimetext = procmime_mimeinfo_new();
5559 mimetext->content = MIMECONTENT_MEM;
5560 mimetext->tmp = TRUE; /* must free content later */
5561 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5562 * and free the data, which we need later. */
5563 mimetext->data.mem = g_strdup(buf);
5564 mimetext->type = MIMETYPE_TEXT;
5565 mimetext->subtype = g_strdup("plain");
5566 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5567 g_strdup(out_codeset));
5569 /* protect trailing spaces when signing message */
5570 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5571 privacy_system_can_sign(compose->privacy_system)) {
5572 encoding = ENC_QUOTED_PRINTABLE;
5575 debug_print("main text: %zd bytes encoded as %s in %d\n",
5576 strlen(buf), out_codeset, encoding);
5578 /* check for line length limit */
5579 if (action == COMPOSE_WRITE_FOR_SEND &&
5580 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5581 check_line_length(buf, 1000, &line) < 0) {
5585 msg = g_strdup_printf
5586 (_("Line %d exceeds the line length limit (998 bytes).\n"
5587 "The contents of the message might be broken on the way to the delivery.\n"
5589 "Send it anyway?"), line + 1);
5590 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5592 if (aval != G_ALERTALTERNATE) {
5598 if (encoding != ENC_UNKNOWN)
5599 procmime_encode_content(mimetext, encoding);
5601 /* append attachment parts */
5602 if (compose_use_attach(compose) && attach_parts) {
5603 MimeInfo *mimempart;
5604 gchar *boundary = NULL;
5605 mimempart = procmime_mimeinfo_new();
5606 mimempart->content = MIMECONTENT_EMPTY;
5607 mimempart->type = MIMETYPE_MULTIPART;
5608 mimempart->subtype = g_strdup("mixed");
5612 boundary = generate_mime_boundary(NULL);
5613 } while (strstr(buf, boundary) != NULL);
5615 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5618 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5620 g_node_append(mimempart->node, mimetext->node);
5621 g_node_append(mimemsg->node, mimempart->node);
5623 if (compose_add_attachments(compose, mimempart) < 0)
5626 g_node_append(mimemsg->node, mimetext->node);
5630 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5631 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5632 /* extract name and address */
5633 if (strstr(spec, " <") && strstr(spec, ">")) {
5634 from_addr = g_strdup(strrchr(spec, '<')+1);
5635 *(strrchr(from_addr, '>')) = '\0';
5636 from_name = g_strdup(spec);
5637 *(strrchr(from_name, '<')) = '\0';
5644 /* sign message if sending */
5645 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5646 privacy_system_can_sign(compose->privacy_system))
5647 if (!privacy_sign(compose->privacy_system, mimemsg,
5648 compose->account, from_addr)) {
5655 procmime_write_mimeinfo(mimemsg, fp);
5657 procmime_mimeinfo_free_all(mimemsg);
5662 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5664 GtkTextBuffer *buffer;
5665 GtkTextIter start, end;
5670 if ((fp = g_fopen(file, "wb")) == NULL) {
5671 FILE_OP_ERROR(file, "fopen");
5675 /* chmod for security */
5676 if (change_file_mode_rw(fp, file) < 0) {
5677 FILE_OP_ERROR(file, "chmod");
5678 g_warning("can't change file mode\n");
5681 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5682 gtk_text_buffer_get_start_iter(buffer, &start);
5683 gtk_text_buffer_get_end_iter(buffer, &end);
5684 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5686 chars = conv_codeset_strdup
5687 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5690 if (!chars) return -1;
5693 len = strlen(chars);
5694 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5695 FILE_OP_ERROR(file, "fwrite");
5704 if (fclose(fp) == EOF) {
5705 FILE_OP_ERROR(file, "fclose");
5712 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5715 MsgInfo *msginfo = compose->targetinfo;
5717 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5718 if (!msginfo) return -1;
5720 if (!force && MSG_IS_LOCKED(msginfo->flags))
5723 item = msginfo->folder;
5724 cm_return_val_if_fail(item != NULL, -1);
5726 if (procmsg_msg_exist(msginfo) &&
5727 (folder_has_parent_of_type(item, F_QUEUE) ||
5728 folder_has_parent_of_type(item, F_DRAFT)
5729 || msginfo == compose->autosaved_draft)) {
5730 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5731 g_warning("can't remove the old message\n");
5734 debug_print("removed reedit target %d\n", msginfo->msgnum);
5741 static void compose_remove_draft(Compose *compose)
5744 MsgInfo *msginfo = compose->targetinfo;
5745 drafts = account_get_special_folder(compose->account, F_DRAFT);
5747 if (procmsg_msg_exist(msginfo)) {
5748 folder_item_remove_msg(drafts, msginfo->msgnum);
5753 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5754 gboolean remove_reedit_target)
5756 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5759 static gboolean compose_warn_encryption(Compose *compose)
5761 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5762 AlertValue val = G_ALERTALTERNATE;
5764 if (warning == NULL)
5767 val = alertpanel_full(_("Encryption warning"), warning,
5768 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5769 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5770 if (val & G_ALERTDISABLE) {
5771 val &= ~G_ALERTDISABLE;
5772 if (val == G_ALERTALTERNATE)
5773 privacy_inhibit_encrypt_warning(compose->privacy_system,
5777 if (val == G_ALERTALTERNATE) {
5784 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5785 gchar **msgpath, gboolean check_subject,
5786 gboolean remove_reedit_target)
5793 PrefsAccount *mailac = NULL, *newsac = NULL;
5794 gboolean err = FALSE;
5796 debug_print("queueing message...\n");
5797 cm_return_val_if_fail(compose->account != NULL, -1);
5799 if (compose_check_entries(compose, check_subject) == FALSE) {
5800 if (compose->batch) {
5801 gtk_widget_show_all(compose->window);
5806 if (!compose->to_list && !compose->newsgroup_list) {
5807 g_warning("can't get recipient list.");
5811 if (compose->to_list) {
5812 if (compose->account->protocol != A_NNTP)
5813 mailac = compose->account;
5814 else if (cur_account && cur_account->protocol != A_NNTP)
5815 mailac = cur_account;
5816 else if (!(mailac = compose_current_mail_account())) {
5817 alertpanel_error(_("No account for sending mails available!"));
5822 if (compose->newsgroup_list) {
5823 if (compose->account->protocol == A_NNTP)
5824 newsac = compose->account;
5826 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5831 /* write queue header */
5832 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5833 G_DIR_SEPARATOR, compose, (guint) rand());
5834 debug_print("queuing to %s\n", tmp);
5835 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5836 FILE_OP_ERROR(tmp, "fopen");
5841 if (change_file_mode_rw(fp, tmp) < 0) {
5842 FILE_OP_ERROR(tmp, "chmod");
5843 g_warning("can't change file mode\n");
5846 /* queueing variables */
5847 err |= (fprintf(fp, "AF:\n") < 0);
5848 err |= (fprintf(fp, "NF:0\n") < 0);
5849 err |= (fprintf(fp, "PS:10\n") < 0);
5850 err |= (fprintf(fp, "SRH:1\n") < 0);
5851 err |= (fprintf(fp, "SFN:\n") < 0);
5852 err |= (fprintf(fp, "DSR:\n") < 0);
5854 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5856 err |= (fprintf(fp, "MID:\n") < 0);
5857 err |= (fprintf(fp, "CFG:\n") < 0);
5858 err |= (fprintf(fp, "PT:0\n") < 0);
5859 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5860 err |= (fprintf(fp, "RQ:\n") < 0);
5862 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5864 err |= (fprintf(fp, "SSV:\n") < 0);
5866 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5868 err |= (fprintf(fp, "NSV:\n") < 0);
5869 err |= (fprintf(fp, "SSH:\n") < 0);
5870 /* write recepient list */
5871 if (compose->to_list) {
5872 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5873 for (cur = compose->to_list->next; cur != NULL;
5875 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5876 err |= (fprintf(fp, "\n") < 0);
5878 /* write newsgroup list */
5879 if (compose->newsgroup_list) {
5880 err |= (fprintf(fp, "NG:") < 0);
5881 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5882 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5883 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5884 err |= (fprintf(fp, "\n") < 0);
5886 /* Sylpheed account IDs */
5888 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5890 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5893 if (compose->privacy_system != NULL) {
5894 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5895 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5896 if (compose->use_encryption) {
5898 if (!compose_warn_encryption(compose)) {
5904 if (mailac && mailac->encrypt_to_self) {
5905 GSList *tmp_list = g_slist_copy(compose->to_list);
5906 tmp_list = g_slist_append(tmp_list, compose->account->address);
5907 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5908 g_slist_free(tmp_list);
5910 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5912 if (encdata != NULL) {
5913 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5914 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5915 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5917 } /* else we finally dont want to encrypt */
5919 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5920 /* and if encdata was null, it means there's been a problem in
5923 g_warning("failed to write queue message");
5933 /* Save copy folder */
5934 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5935 gchar *savefolderid;
5937 savefolderid = compose_get_save_to(compose);
5938 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5939 g_free(savefolderid);
5941 /* Save copy folder */
5942 if (compose->return_receipt) {
5943 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5945 /* Message-ID of message replying to */
5946 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5949 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5950 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5953 /* Message-ID of message forwarding to */
5954 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5957 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5958 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5962 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5963 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5965 /* end of headers */
5966 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5968 if (compose->redirect_filename != NULL) {
5969 if (compose_redirect_write_to_file(compose, fp) < 0) {
5977 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5981 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5985 g_warning("failed to write queue message\n");
5991 if (fclose(fp) == EOF) {
5992 FILE_OP_ERROR(tmp, "fclose");
5998 if (item && *item) {
6001 queue = account_get_special_folder(compose->account, F_QUEUE);
6004 g_warning("can't find queue folder\n");
6009 folder_item_scan(queue);
6010 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6011 g_warning("can't queue the message\n");
6017 if (msgpath == NULL) {
6023 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6024 compose_remove_reedit_target(compose, FALSE);
6027 if ((msgnum != NULL) && (item != NULL)) {
6035 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6038 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6040 struct stat statbuf;
6041 gchar *type, *subtype;
6042 GtkTreeModel *model;
6045 model = gtk_tree_view_get_model(tree_view);
6047 if (!gtk_tree_model_get_iter_first(model, &iter))
6050 gtk_tree_model_get(model, &iter,
6054 if (!is_file_exist(ainfo->file)) {
6055 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6056 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6057 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6059 if (val == G_ALERTDEFAULT) {
6064 mimepart = procmime_mimeinfo_new();
6065 mimepart->content = MIMECONTENT_FILE;
6066 mimepart->data.filename = g_strdup(ainfo->file);
6067 mimepart->tmp = FALSE; /* or we destroy our attachment */
6068 mimepart->offset = 0;
6070 g_stat(ainfo->file, &statbuf);
6071 mimepart->length = statbuf.st_size;
6073 type = g_strdup(ainfo->content_type);
6075 if (!strchr(type, '/')) {
6077 type = g_strdup("application/octet-stream");
6080 subtype = strchr(type, '/') + 1;
6081 *(subtype - 1) = '\0';
6082 mimepart->type = procmime_get_media_type(type);
6083 mimepart->subtype = g_strdup(subtype);
6086 if (mimepart->type == MIMETYPE_MESSAGE &&
6087 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6088 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6089 } else if (mimepart->type == MIMETYPE_TEXT) {
6090 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6091 /* Text parts with no name come from multipart/alternative
6092 * forwards. Make sure the recipient won't look at the
6093 * original HTML part by mistake. */
6094 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6095 ainfo->name = g_strdup_printf(_("Original %s part"),
6099 g_hash_table_insert(mimepart->typeparameters,
6100 g_strdup("charset"), g_strdup(ainfo->charset));
6102 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6103 if (mimepart->type == MIMETYPE_APPLICATION &&
6104 !strcmp2(mimepart->subtype, "octet-stream"))
6105 g_hash_table_insert(mimepart->typeparameters,
6106 g_strdup("name"), g_strdup(ainfo->name));
6107 g_hash_table_insert(mimepart->dispositionparameters,
6108 g_strdup("filename"), g_strdup(ainfo->name));
6109 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6112 if (mimepart->type == MIMETYPE_MESSAGE
6113 || mimepart->type == MIMETYPE_MULTIPART)
6114 ainfo->encoding = ENC_BINARY;
6115 else if (compose->use_signing) {
6116 if (ainfo->encoding == ENC_7BIT)
6117 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6118 else if (ainfo->encoding == ENC_8BIT)
6119 ainfo->encoding = ENC_BASE64;
6124 procmime_encode_content(mimepart, ainfo->encoding);
6126 g_node_append(parent->node, mimepart->node);
6127 } while (gtk_tree_model_iter_next(model, &iter));
6132 static gchar *compose_quote_list_of_addresses(gchar *str)
6134 GSList *list = NULL, *item = NULL;
6135 gchar *qname = NULL, *faddr = NULL, *result = NULL;
6137 list = address_list_append_with_comments(list, str);
6138 for (item = list; item != NULL; item = item->next) {
6139 gchar *spec = item->data;
6140 gchar *endofname = strstr(spec, " <");
6141 if (endofname != NULL) {
6143 QUOTE_IF_REQUIRED_NORMAL(qname, spec, return NULL);
6145 if (*qname != *spec) { /* has been quoted, compute new */
6146 gchar *addr = g_strdup(endofname);
6147 gchar *name = g_strdup(qname);
6148 faddr = g_strconcat(name, addr, NULL);
6151 debug_print("new auto-quoted address: '%s'", faddr);
6155 result = g_strdup((faddr != NULL)? faddr: spec);
6157 result = g_strconcat(result,
6159 (faddr != NULL)? faddr: spec,
6162 if (faddr != NULL) {
6167 slist_free_strings_full(list);
6172 #define IS_IN_CUSTOM_HEADER(header) \
6173 (compose->account->add_customhdr && \
6174 custom_header_find(compose->account->customhdr_list, header) != NULL)
6176 static void compose_add_headerfield_from_headerlist(Compose *compose,
6178 const gchar *fieldname,
6179 const gchar *seperator)
6181 gchar *str, *fieldname_w_colon;
6182 gboolean add_field = FALSE;
6184 ComposeHeaderEntry *headerentry;
6185 const gchar *headerentryname;
6186 const gchar *trans_fieldname;
6189 if (IS_IN_CUSTOM_HEADER(fieldname))
6192 debug_print("Adding %s-fields\n", fieldname);
6194 fieldstr = g_string_sized_new(64);
6196 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6197 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6199 for (list = compose->header_list; list; list = list->next) {
6200 headerentry = ((ComposeHeaderEntry *)list->data);
6201 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6203 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6204 gchar * ustr = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6206 str = compose_quote_list_of_addresses(ustr);
6208 if (str != NULL && str[0] != '\0') {
6210 g_string_append(fieldstr, seperator);
6211 g_string_append(fieldstr, str);
6220 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6221 compose_convert_header
6222 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6223 strlen(fieldname) + 2, TRUE);
6224 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6228 g_free(fieldname_w_colon);
6229 g_string_free(fieldstr, TRUE);
6234 static gchar *compose_get_manual_headers_info(Compose *compose)
6236 GString *sh_header = g_string_new(" ");
6238 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6240 for (list = compose->header_list; list; list = list->next) {
6241 ComposeHeaderEntry *headerentry;
6244 gchar *headername_wcolon;
6245 const gchar *headername_trans;
6247 gboolean standard_header = FALSE;
6249 headerentry = ((ComposeHeaderEntry *)list->data);
6251 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6253 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6258 if (!strstr(tmp, ":")) {
6259 headername_wcolon = g_strconcat(tmp, ":", NULL);
6260 headername = g_strdup(tmp);
6262 headername_wcolon = g_strdup(tmp);
6263 headername = g_strdup(strtok(tmp, ":"));
6267 string = std_headers;
6268 while (*string != NULL) {
6269 headername_trans = prefs_common_translated_header_name(*string);
6270 if (!strcmp(headername_trans, headername_wcolon))
6271 standard_header = TRUE;
6274 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6275 g_string_append_printf(sh_header, "%s ", headername);
6277 g_free(headername_wcolon);
6279 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6280 return g_string_free(sh_header, FALSE);
6283 static gchar *compose_get_header(Compose *compose)
6285 gchar buf[BUFFSIZE];
6286 const gchar *entry_str;
6290 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6292 gchar *from_name = NULL, *from_address = NULL;
6295 cm_return_val_if_fail(compose->account != NULL, NULL);
6296 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6298 header = g_string_sized_new(64);
6301 get_rfc822_date(buf, sizeof(buf));
6302 g_string_append_printf(header, "Date: %s\n", buf);
6306 if (compose->account->name && *compose->account->name) {
6308 QUOTE_IF_REQUIRED(buf, compose->account->name);
6309 tmp = g_strdup_printf("%s <%s>",
6310 buf, compose->account->address);
6312 tmp = g_strdup_printf("%s",
6313 compose->account->address);
6315 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6316 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6318 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6319 from_address = g_strdup(compose->account->address);
6321 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6322 /* extract name and address */
6323 if (strstr(spec, " <") && strstr(spec, ">")) {
6324 from_address = g_strdup(strrchr(spec, '<')+1);
6325 *(strrchr(from_address, '>')) = '\0';
6326 from_name = g_strdup(spec);
6327 *(strrchr(from_name, '<')) = '\0';
6330 from_address = g_strdup(spec);
6337 if (from_name && *from_name) {
6338 compose_convert_header
6339 (compose, buf, sizeof(buf), from_name,
6340 strlen("From: "), TRUE);
6341 QUOTE_IF_REQUIRED(name, buf);
6343 g_string_append_printf(header, "From: %s <%s>\n",
6344 name, from_address);
6346 g_string_append_printf(header, "From: %s\n", from_address);
6349 g_free(from_address);
6352 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6355 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6358 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6362 * If this account is a NNTP account remove Bcc header from
6363 * message body since it otherwise will be publicly shown
6365 if (compose->account->protocol != A_NNTP)
6366 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6369 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6371 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6374 compose_convert_header(compose, buf, sizeof(buf), str,
6375 strlen("Subject: "), FALSE);
6376 g_string_append_printf(header, "Subject: %s\n", buf);
6382 if (compose->account->set_domain && compose->account->domain) {
6383 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6384 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6385 g_snprintf(buf, sizeof(buf), "%s",
6386 strchr(compose->account->address, '@') ?
6387 strchr(compose->account->address, '@')+1 :
6388 compose->account->address);
6390 g_snprintf(buf, sizeof(buf), "%s", "");
6393 if (compose->account->gen_msgid) {
6395 if (compose->account->msgid_with_addr) {
6396 addr = compose->account->address;
6398 generate_msgid(buf, sizeof(buf), addr);
6399 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6400 compose->msgid = g_strdup(buf);
6402 compose->msgid = NULL;
6405 if (compose->remove_references == FALSE) {
6407 if (compose->inreplyto && compose->to_list)
6408 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6411 if (compose->references)
6412 g_string_append_printf(header, "References: %s\n", compose->references);
6416 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6419 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6422 if (compose->account->organization &&
6423 strlen(compose->account->organization) &&
6424 !IS_IN_CUSTOM_HEADER("Organization")) {
6425 compose_convert_header(compose, buf, sizeof(buf),
6426 compose->account->organization,
6427 strlen("Organization: "), FALSE);
6428 g_string_append_printf(header, "Organization: %s\n", buf);
6431 /* Program version and system info */
6432 if (compose->account->gen_xmailer &&
6433 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6434 !compose->newsgroup_list) {
6435 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6437 gtk_major_version, gtk_minor_version, gtk_micro_version,
6440 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6441 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6443 gtk_major_version, gtk_minor_version, gtk_micro_version,
6447 /* custom headers */
6448 if (compose->account->add_customhdr) {
6451 for (cur = compose->account->customhdr_list; cur != NULL;
6453 CustomHeader *chdr = (CustomHeader *)cur->data;
6455 if (custom_header_is_allowed(chdr->name)
6456 && chdr->value != NULL
6457 && *(chdr->value) != '\0') {
6458 compose_convert_header
6459 (compose, buf, sizeof(buf),
6461 strlen(chdr->name) + 2, FALSE);
6462 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6467 /* Automatic Faces and X-Faces */
6468 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6469 g_string_append_printf(header, "X-Face: %s\n", buf);
6471 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6472 g_string_append_printf(header, "X-Face: %s\n", buf);
6474 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6475 g_string_append_printf(header, "Face: %s\n", buf);
6477 else if (get_default_face (buf, sizeof(buf)) == 0) {
6478 g_string_append_printf(header, "Face: %s\n", buf);
6482 switch (compose->priority) {
6483 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6484 "X-Priority: 1 (Highest)\n");
6486 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6487 "X-Priority: 2 (High)\n");
6489 case PRIORITY_NORMAL: break;
6490 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6491 "X-Priority: 4 (Low)\n");
6493 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6494 "X-Priority: 5 (Lowest)\n");
6496 default: debug_print("compose: priority unknown : %d\n",
6500 /* Request Return Receipt */
6501 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6502 if (compose->return_receipt) {
6503 if (compose->account->name
6504 && *compose->account->name) {
6505 compose_convert_header(compose, buf, sizeof(buf),
6506 compose->account->name,
6507 strlen("Disposition-Notification-To: "),
6509 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6511 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6515 /* get special headers */
6516 for (list = compose->header_list; list; list = list->next) {
6517 ComposeHeaderEntry *headerentry;
6520 gchar *headername_wcolon;
6521 const gchar *headername_trans;
6524 gboolean standard_header = FALSE;
6526 headerentry = ((ComposeHeaderEntry *)list->data);
6528 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6530 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6535 if (!strstr(tmp, ":")) {
6536 headername_wcolon = g_strconcat(tmp, ":", NULL);
6537 headername = g_strdup(tmp);
6539 headername_wcolon = g_strdup(tmp);
6540 headername = g_strdup(strtok(tmp, ":"));
6544 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6545 Xstrdup_a(headervalue, entry_str, return NULL);
6546 subst_char(headervalue, '\r', ' ');
6547 subst_char(headervalue, '\n', ' ');
6548 string = std_headers;
6549 while (*string != NULL) {
6550 headername_trans = prefs_common_translated_header_name(*string);
6551 if (!strcmp(headername_trans, headername_wcolon))
6552 standard_header = TRUE;
6555 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6556 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6559 g_free(headername_wcolon);
6563 g_string_free(header, FALSE);
6568 #undef IS_IN_CUSTOM_HEADER
6570 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6571 gint header_len, gboolean addr_field)
6573 gchar *tmpstr = NULL;
6574 const gchar *out_codeset = NULL;
6576 cm_return_if_fail(src != NULL);
6577 cm_return_if_fail(dest != NULL);
6579 if (len < 1) return;
6581 tmpstr = g_strdup(src);
6583 subst_char(tmpstr, '\n', ' ');
6584 subst_char(tmpstr, '\r', ' ');
6587 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6588 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6589 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6594 codeconv_set_strict(TRUE);
6595 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6596 conv_get_charset_str(compose->out_encoding));
6597 codeconv_set_strict(FALSE);
6599 if (!dest || *dest == '\0') {
6600 gchar *test_conv_global_out = NULL;
6601 gchar *test_conv_reply = NULL;
6603 /* automatic mode. be automatic. */
6604 codeconv_set_strict(TRUE);
6606 out_codeset = conv_get_outgoing_charset_str();
6608 debug_print("trying to convert to %s\n", out_codeset);
6609 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6612 if (!test_conv_global_out && compose->orig_charset
6613 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6614 out_codeset = compose->orig_charset;
6615 debug_print("failure; trying to convert to %s\n", out_codeset);
6616 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6619 if (!test_conv_global_out && !test_conv_reply) {
6621 out_codeset = CS_INTERNAL;
6622 debug_print("finally using %s\n", out_codeset);
6624 g_free(test_conv_global_out);
6625 g_free(test_conv_reply);
6626 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6628 codeconv_set_strict(FALSE);
6633 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6637 cm_return_if_fail(user_data != NULL);
6639 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6640 g_strstrip(address);
6641 if (*address != '\0') {
6642 gchar *name = procheader_get_fromname(address);
6643 extract_address(address);
6644 #ifndef USE_NEW_ADDRBOOK
6645 addressbook_add_contact(name, address, NULL, NULL);
6647 debug_print("%s: %s\n", name, address);
6648 if (addressadd_selection(name, address, NULL, NULL)) {
6649 debug_print( "addressbook_add_contact - added\n" );
6656 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6658 GtkWidget *menuitem;
6661 cm_return_if_fail(menu != NULL);
6662 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6664 menuitem = gtk_separator_menu_item_new();
6665 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6666 gtk_widget_show(menuitem);
6668 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6669 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6671 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6672 g_strstrip(address);
6673 if (*address == '\0') {
6674 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6677 g_signal_connect(G_OBJECT(menuitem), "activate",
6678 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6679 gtk_widget_show(menuitem);
6682 void compose_add_extra_header(gchar *header, GtkListStore *model)
6685 if (strcmp(header, "")) {
6686 COMBOBOX_ADD(model, header, COMPOSE_TO);
6690 void compose_add_extra_header_entries(GtkListStore *model)
6694 gchar buf[BUFFSIZE];
6697 if (extra_headers == NULL) {
6698 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6699 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6700 debug_print("extra headers file not found\n");
6701 goto extra_headers_done;
6703 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6704 lastc = strlen(buf) - 1; /* remove trailing control chars */
6705 while (lastc >= 0 && buf[lastc] != ':')
6706 buf[lastc--] = '\0';
6707 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6708 buf[lastc] = '\0'; /* remove trailing : for comparison */
6709 if (custom_header_is_allowed(buf)) {
6711 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6714 g_message("disallowed extra header line: %s\n", buf);
6718 g_message("invalid extra header line: %s\n", buf);
6724 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6725 extra_headers = g_slist_reverse(extra_headers);
6727 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6730 static void compose_create_header_entry(Compose *compose)
6732 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6739 const gchar *header = NULL;
6740 ComposeHeaderEntry *headerentry;
6741 gboolean standard_header = FALSE;
6742 GtkListStore *model;
6744 #if !(GTK_CHECK_VERSION(2,12,0))
6745 GtkTooltips *tips = compose->tooltips;
6748 headerentry = g_new0(ComposeHeaderEntry, 1);
6750 /* Combo box model */
6751 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6752 #if !GTK_CHECK_VERSION(2, 24, 0)
6753 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6755 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6757 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6759 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6761 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6762 COMPOSE_NEWSGROUPS);
6763 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6765 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6766 COMPOSE_FOLLOWUPTO);
6767 compose_add_extra_header_entries(model);
6770 #if GTK_CHECK_VERSION(2, 24, 0)
6771 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6772 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6773 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6774 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6775 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6777 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6778 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6779 G_CALLBACK(compose_grab_focus_cb), compose);
6780 gtk_widget_show(combo);
6783 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6784 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6787 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6788 compose->header_nextrow, compose->header_nextrow+1,
6789 GTK_SHRINK, GTK_FILL, 0, 0);
6790 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6791 const gchar *last_header_entry = gtk_entry_get_text(
6792 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6794 while (*string != NULL) {
6795 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6796 standard_header = TRUE;
6799 if (standard_header)
6800 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6802 if (!compose->header_last || !standard_header) {
6803 switch(compose->account->protocol) {
6805 header = prefs_common_translated_header_name("Newsgroups:");
6808 header = prefs_common_translated_header_name("To:");
6813 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6815 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6816 G_CALLBACK(compose_grab_focus_cb), compose);
6818 /* Entry field with cleanup button */
6819 button = gtk_button_new();
6820 gtk_button_set_image(GTK_BUTTON(button),
6821 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6822 gtk_widget_show(button);
6823 CLAWS_SET_TIP(button,
6824 _("Delete entry contents"));
6825 entry = gtk_entry_new();
6826 gtk_widget_show(entry);
6827 CLAWS_SET_TIP(entry,
6828 _("Use <tab> to autocomplete from addressbook"));
6829 hbox = gtk_hbox_new (FALSE, 0);
6830 gtk_widget_show(hbox);
6831 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6832 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6833 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6834 compose->header_nextrow, compose->header_nextrow+1,
6835 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6837 g_signal_connect(G_OBJECT(entry), "key-press-event",
6838 G_CALLBACK(compose_headerentry_key_press_event_cb),
6840 g_signal_connect(G_OBJECT(entry), "changed",
6841 G_CALLBACK(compose_headerentry_changed_cb),
6843 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6844 G_CALLBACK(compose_grab_focus_cb), compose);
6846 g_signal_connect(G_OBJECT(button), "clicked",
6847 G_CALLBACK(compose_headerentry_button_clicked_cb),
6851 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6852 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6853 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6854 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6855 G_CALLBACK(compose_header_drag_received_cb),
6857 g_signal_connect(G_OBJECT(entry), "drag-drop",
6858 G_CALLBACK(compose_drag_drop),
6860 g_signal_connect(G_OBJECT(entry), "populate-popup",
6861 G_CALLBACK(compose_entry_popup_extend),
6864 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6866 headerentry->compose = compose;
6867 headerentry->combo = combo;
6868 headerentry->entry = entry;
6869 headerentry->button = button;
6870 headerentry->hbox = hbox;
6871 headerentry->headernum = compose->header_nextrow;
6872 headerentry->type = PREF_NONE;
6874 compose->header_nextrow++;
6875 compose->header_last = headerentry;
6876 compose->header_list =
6877 g_slist_append(compose->header_list,
6881 static void compose_add_header_entry(Compose *compose, const gchar *header,
6882 gchar *text, ComposePrefType pref_type)
6884 ComposeHeaderEntry *last_header = compose->header_last;
6885 gchar *tmp = g_strdup(text), *email;
6886 gboolean replyto_hdr;
6888 replyto_hdr = (!strcasecmp(header,
6889 prefs_common_translated_header_name("Reply-To:")) ||
6891 prefs_common_translated_header_name("Followup-To:")) ||
6893 prefs_common_translated_header_name("In-Reply-To:")));
6895 extract_address(tmp);
6896 email = g_utf8_strdown(tmp, -1);
6898 if (replyto_hdr == FALSE &&
6899 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6901 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6902 header, text, (gint) pref_type);
6908 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6909 gtk_entry_set_text(GTK_ENTRY(
6910 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6912 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6913 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6914 last_header->type = pref_type;
6916 if (replyto_hdr == FALSE)
6917 g_hash_table_insert(compose->email_hashtable, email,
6918 GUINT_TO_POINTER(1));
6925 static void compose_destroy_headerentry(Compose *compose,
6926 ComposeHeaderEntry *headerentry)
6928 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6931 extract_address(text);
6932 email = g_utf8_strdown(text, -1);
6933 g_hash_table_remove(compose->email_hashtable, email);
6937 gtk_widget_destroy(headerentry->combo);
6938 gtk_widget_destroy(headerentry->entry);
6939 gtk_widget_destroy(headerentry->button);
6940 gtk_widget_destroy(headerentry->hbox);
6941 g_free(headerentry);
6944 static void compose_remove_header_entries(Compose *compose)
6947 for (list = compose->header_list; list; list = list->next)
6948 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6950 compose->header_last = NULL;
6951 g_slist_free(compose->header_list);
6952 compose->header_list = NULL;
6953 compose->header_nextrow = 1;
6954 compose_create_header_entry(compose);
6957 static GtkWidget *compose_create_header(Compose *compose)
6959 GtkWidget *from_optmenu_hbox;
6960 GtkWidget *header_scrolledwin_main;
6961 GtkWidget *header_table_main;
6962 GtkWidget *header_scrolledwin;
6963 GtkWidget *header_table;
6965 /* parent with account selection and from header */
6966 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6967 gtk_widget_show(header_scrolledwin_main);
6968 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6970 header_table_main = gtk_table_new(2, 2, FALSE);
6971 gtk_widget_show(header_table_main);
6972 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6973 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6974 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6976 from_optmenu_hbox = compose_account_option_menu_create(compose);
6977 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6978 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6980 /* child with header labels and entries */
6981 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6982 gtk_widget_show(header_scrolledwin);
6983 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6985 header_table = gtk_table_new(2, 2, FALSE);
6986 gtk_widget_show(header_table);
6987 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6988 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6989 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6991 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6992 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6994 compose->header_table = header_table;
6995 compose->header_list = NULL;
6996 compose->header_nextrow = 0;
6998 compose_create_header_entry(compose);
7000 compose->table = NULL;
7002 return header_scrolledwin_main;
7005 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
7007 Compose *compose = (Compose *)data;
7008 GdkEventButton event;
7011 event.time = gtk_get_current_event_time();
7013 return attach_button_pressed(compose->attach_clist, &event, compose);
7016 static GtkWidget *compose_create_attach(Compose *compose)
7018 GtkWidget *attach_scrwin;
7019 GtkWidget *attach_clist;
7021 GtkListStore *store;
7022 GtkCellRenderer *renderer;
7023 GtkTreeViewColumn *column;
7024 GtkTreeSelection *selection;
7026 /* attachment list */
7027 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
7028 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
7029 GTK_POLICY_AUTOMATIC,
7030 GTK_POLICY_AUTOMATIC);
7031 gtk_widget_set_size_request(attach_scrwin, -1, 80);
7033 store = gtk_list_store_new(N_ATTACH_COLS,
7039 G_TYPE_AUTO_POINTER,
7041 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7042 (GTK_TREE_MODEL(store)));
7043 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7044 g_object_unref(store);
7046 renderer = gtk_cell_renderer_text_new();
7047 column = gtk_tree_view_column_new_with_attributes
7048 (_("Mime type"), renderer, "text",
7049 COL_MIMETYPE, NULL);
7050 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7052 renderer = gtk_cell_renderer_text_new();
7053 column = gtk_tree_view_column_new_with_attributes
7054 (_("Size"), renderer, "text",
7056 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7058 renderer = gtk_cell_renderer_text_new();
7059 column = gtk_tree_view_column_new_with_attributes
7060 (_("Name"), renderer, "text",
7062 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7064 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7065 prefs_common.use_stripes_everywhere);
7066 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7067 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7069 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7070 G_CALLBACK(attach_selected), compose);
7071 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7072 G_CALLBACK(attach_button_pressed), compose);
7073 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7074 G_CALLBACK(popup_attach_button_pressed), compose);
7075 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7076 G_CALLBACK(attach_key_pressed), compose);
7079 gtk_drag_dest_set(attach_clist,
7080 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7081 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7082 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7083 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7084 G_CALLBACK(compose_attach_drag_received_cb),
7086 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7087 G_CALLBACK(compose_drag_drop),
7090 compose->attach_scrwin = attach_scrwin;
7091 compose->attach_clist = attach_clist;
7093 return attach_scrwin;
7096 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7097 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7099 static GtkWidget *compose_create_others(Compose *compose)
7102 GtkWidget *savemsg_checkbtn;
7103 GtkWidget *savemsg_combo;
7104 GtkWidget *savemsg_select;
7107 gchar *folderidentifier;
7109 /* Table for settings */
7110 table = gtk_table_new(3, 1, FALSE);
7111 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7112 gtk_widget_show(table);
7113 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7116 /* Save Message to folder */
7117 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7118 gtk_widget_show(savemsg_checkbtn);
7119 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7120 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7121 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7123 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7124 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7126 #if !GTK_CHECK_VERSION(2, 24, 0)
7127 savemsg_combo = gtk_combo_box_entry_new_text();
7129 savemsg_combo = gtk_combo_box_text_new_with_entry();
7131 compose->savemsg_checkbtn = savemsg_checkbtn;
7132 compose->savemsg_combo = savemsg_combo;
7133 gtk_widget_show(savemsg_combo);
7135 if (prefs_common.compose_save_to_history)
7136 #if !GTK_CHECK_VERSION(2, 24, 0)
7137 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7138 prefs_common.compose_save_to_history);
7140 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7141 prefs_common.compose_save_to_history);
7143 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7144 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7145 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7146 G_CALLBACK(compose_grab_focus_cb), compose);
7147 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7148 folderidentifier = folder_item_get_identifier(account_get_special_folder
7149 (compose->account, F_OUTBOX));
7150 compose_set_save_to(compose, folderidentifier);
7151 g_free(folderidentifier);
7154 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7155 gtk_widget_show(savemsg_select);
7156 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7157 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7158 G_CALLBACK(compose_savemsg_select_cb),
7164 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7166 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7167 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7170 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7175 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7178 path = folder_item_get_identifier(dest);
7180 compose_set_save_to(compose, path);
7184 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7185 GdkAtom clip, GtkTextIter *insert_place);
7188 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7192 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7194 if (event->button == 3) {
7196 GtkTextIter sel_start, sel_end;
7197 gboolean stuff_selected;
7199 /* move the cursor to allow GtkAspell to check the word
7200 * under the mouse */
7201 if (event->x && event->y) {
7202 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7203 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7205 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7208 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7209 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7212 stuff_selected = gtk_text_buffer_get_selection_bounds(
7214 &sel_start, &sel_end);
7216 gtk_text_buffer_place_cursor (buffer, &iter);
7217 /* reselect stuff */
7219 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7220 gtk_text_buffer_select_range(buffer,
7221 &sel_start, &sel_end);
7223 return FALSE; /* pass the event so that the right-click goes through */
7226 if (event->button == 2) {
7231 /* get the middle-click position to paste at the correct place */
7232 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7233 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7235 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7238 entry_paste_clipboard(compose, text,
7239 prefs_common.linewrap_pastes,
7240 GDK_SELECTION_PRIMARY, &iter);
7248 static void compose_spell_menu_changed(void *data)
7250 Compose *compose = (Compose *)data;
7252 GtkWidget *menuitem;
7253 GtkWidget *parent_item;
7254 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7257 if (compose->gtkaspell == NULL)
7260 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7261 "/Menu/Spelling/Options");
7263 /* setting the submenu removes /Spelling/Options from the factory
7264 * so we need to save it */
7266 if (parent_item == NULL) {
7267 parent_item = compose->aspell_options_menu;
7268 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7270 compose->aspell_options_menu = parent_item;
7272 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7274 spell_menu = g_slist_reverse(spell_menu);
7275 for (items = spell_menu;
7276 items; items = items->next) {
7277 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7278 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7279 gtk_widget_show(GTK_WIDGET(menuitem));
7281 g_slist_free(spell_menu);
7283 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7284 gtk_widget_show(parent_item);
7287 static void compose_dict_changed(void *data)
7289 Compose *compose = (Compose *) data;
7291 if(compose->gtkaspell &&
7292 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7295 gtkaspell_highlight_all(compose->gtkaspell);
7296 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7300 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7302 Compose *compose = (Compose *)data;
7303 GdkEventButton event;
7306 event.time = gtk_get_current_event_time();
7310 return text_clicked(compose->text, &event, compose);
7313 static gboolean compose_force_window_origin = TRUE;
7314 static Compose *compose_create(PrefsAccount *account,
7323 GtkWidget *handlebox;
7325 GtkWidget *notebook;
7327 GtkWidget *attach_hbox;
7328 GtkWidget *attach_lab1;
7329 GtkWidget *attach_lab2;
7334 GtkWidget *subject_hbox;
7335 GtkWidget *subject_frame;
7336 GtkWidget *subject_entry;
7340 GtkWidget *edit_vbox;
7341 GtkWidget *ruler_hbox;
7343 GtkWidget *scrolledwin;
7345 GtkTextBuffer *buffer;
7346 GtkClipboard *clipboard;
7348 UndoMain *undostruct;
7350 GtkWidget *popupmenu;
7351 GtkWidget *tmpl_menu;
7352 GtkActionGroup *action_group = NULL;
7355 GtkAspell * gtkaspell = NULL;
7358 static GdkGeometry geometry;
7360 cm_return_val_if_fail(account != NULL, NULL);
7362 debug_print("Creating compose window...\n");
7363 compose = g_new0(Compose, 1);
7365 compose->batch = batch;
7366 compose->account = account;
7367 compose->folder = folder;
7369 compose->mutex = cm_mutex_new();
7370 compose->set_cursor_pos = -1;
7372 #if !(GTK_CHECK_VERSION(2,12,0))
7373 compose->tooltips = tips;
7376 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7378 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7379 gtk_widget_set_size_request(window, prefs_common.compose_width,
7380 prefs_common.compose_height);
7382 if (!geometry.max_width) {
7383 geometry.max_width = gdk_screen_width();
7384 geometry.max_height = gdk_screen_height();
7387 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7388 &geometry, GDK_HINT_MAX_SIZE);
7389 if (!geometry.min_width) {
7390 geometry.min_width = 600;
7391 geometry.min_height = 440;
7393 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7394 &geometry, GDK_HINT_MIN_SIZE);
7396 #ifndef GENERIC_UMPC
7397 if (compose_force_window_origin)
7398 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7399 prefs_common.compose_y);
7401 g_signal_connect(G_OBJECT(window), "delete_event",
7402 G_CALLBACK(compose_delete_cb), compose);
7403 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7404 gtk_widget_realize(window);
7406 gtkut_widget_set_composer_icon(window);
7408 vbox = gtk_vbox_new(FALSE, 0);
7409 gtk_container_add(GTK_CONTAINER(window), vbox);
7411 compose->ui_manager = gtk_ui_manager_new();
7412 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7413 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7414 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7415 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7416 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7417 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7418 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7419 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7420 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7421 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7423 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7431 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7432 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7437 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7445 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7446 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7539 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)
7540 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)
7541 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7546 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)
7547 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)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7552 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)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7555 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7556 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)
7557 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7559 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7561 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7562 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)
7563 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7564 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7565 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7567 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7568 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)
7569 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)
7570 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7571 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7573 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7574 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7575 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7576 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7577 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7578 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7580 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7581 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7582 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)
7584 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7585 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7586 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7590 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7591 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7592 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7593 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7594 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7595 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7598 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7600 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7601 gtk_widget_show_all(menubar);
7603 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7604 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7606 if (prefs_common.toolbar_detachable) {
7607 handlebox = gtk_handle_box_new();
7609 handlebox = gtk_hbox_new(FALSE, 0);
7611 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7613 gtk_widget_realize(handlebox);
7614 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7617 vbox2 = gtk_vbox_new(FALSE, 2);
7618 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7619 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7622 notebook = gtk_notebook_new();
7623 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7624 gtk_widget_show(notebook);
7626 /* header labels and entries */
7627 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7628 compose_create_header(compose),
7629 gtk_label_new_with_mnemonic(_("Hea_der")));
7630 /* attachment list */
7631 attach_hbox = gtk_hbox_new(FALSE, 0);
7632 gtk_widget_show(attach_hbox);
7634 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7635 gtk_widget_show(attach_lab1);
7636 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7638 attach_lab2 = gtk_label_new("");
7639 gtk_widget_show(attach_lab2);
7640 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7642 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7643 compose_create_attach(compose),
7646 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7647 compose_create_others(compose),
7648 gtk_label_new_with_mnemonic(_("Othe_rs")));
7651 subject_hbox = gtk_hbox_new(FALSE, 0);
7652 gtk_widget_show(subject_hbox);
7654 subject_frame = gtk_frame_new(NULL);
7655 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7656 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7657 gtk_widget_show(subject_frame);
7659 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7660 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7661 gtk_widget_show(subject);
7663 label = gtk_label_new(_("Subject:"));
7664 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7665 gtk_widget_show(label);
7668 subject_entry = claws_spell_entry_new();
7670 subject_entry = gtk_entry_new();
7672 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7673 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7674 G_CALLBACK(compose_grab_focus_cb), compose);
7675 gtk_widget_show(subject_entry);
7676 compose->subject_entry = subject_entry;
7677 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7679 edit_vbox = gtk_vbox_new(FALSE, 0);
7681 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7684 ruler_hbox = gtk_hbox_new(FALSE, 0);
7685 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7687 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7688 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7689 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7693 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7694 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7695 GTK_POLICY_AUTOMATIC,
7696 GTK_POLICY_AUTOMATIC);
7697 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7699 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7701 text = gtk_text_view_new();
7702 if (prefs_common.show_compose_margin) {
7703 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7704 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7706 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7707 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7708 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7709 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7710 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7712 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7713 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7714 G_CALLBACK(compose_notebook_size_alloc), compose);
7715 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7716 G_CALLBACK(compose_edit_size_alloc),
7718 g_signal_connect(G_OBJECT(buffer), "changed",
7719 G_CALLBACK(compose_changed_cb), compose);
7720 g_signal_connect(G_OBJECT(text), "grab_focus",
7721 G_CALLBACK(compose_grab_focus_cb), compose);
7722 g_signal_connect(G_OBJECT(buffer), "insert_text",
7723 G_CALLBACK(text_inserted), compose);
7724 g_signal_connect(G_OBJECT(text), "button_press_event",
7725 G_CALLBACK(text_clicked), compose);
7726 g_signal_connect(G_OBJECT(text), "popup-menu",
7727 G_CALLBACK(compose_popup_menu), compose);
7728 g_signal_connect(G_OBJECT(subject_entry), "changed",
7729 G_CALLBACK(compose_changed_cb), compose);
7730 g_signal_connect(G_OBJECT(subject_entry), "activate",
7731 G_CALLBACK(compose_subject_entry_activated), compose);
7734 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7735 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7736 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7737 g_signal_connect(G_OBJECT(text), "drag_data_received",
7738 G_CALLBACK(compose_insert_drag_received_cb),
7740 g_signal_connect(G_OBJECT(text), "drag-drop",
7741 G_CALLBACK(compose_drag_drop),
7743 g_signal_connect(G_OBJECT(text), "key-press-event",
7744 G_CALLBACK(completion_set_focus_to_subject),
7746 gtk_widget_show_all(vbox);
7748 /* pane between attach clist and text */
7749 paned = gtk_vpaned_new();
7750 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7751 gtk_paned_add1(GTK_PANED(paned), notebook);
7752 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7753 gtk_widget_show_all(paned);
7756 if (prefs_common.textfont) {
7757 PangoFontDescription *font_desc;
7759 font_desc = pango_font_description_from_string
7760 (prefs_common.textfont);
7762 gtk_widget_modify_font(text, font_desc);
7763 pango_font_description_free(font_desc);
7767 gtk_action_group_add_actions(action_group, compose_popup_entries,
7768 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7769 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7770 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7771 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7772 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7773 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7774 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7776 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7778 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7779 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7780 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7782 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7784 undostruct = undo_init(text);
7785 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7788 address_completion_start(window);
7790 compose->window = window;
7791 compose->vbox = vbox;
7792 compose->menubar = menubar;
7793 compose->handlebox = handlebox;
7795 compose->vbox2 = vbox2;
7797 compose->paned = paned;
7799 compose->attach_label = attach_lab2;
7801 compose->notebook = notebook;
7802 compose->edit_vbox = edit_vbox;
7803 compose->ruler_hbox = ruler_hbox;
7804 compose->ruler = ruler;
7805 compose->scrolledwin = scrolledwin;
7806 compose->text = text;
7808 compose->focused_editable = NULL;
7810 compose->popupmenu = popupmenu;
7812 compose->tmpl_menu = tmpl_menu;
7814 compose->mode = mode;
7815 compose->rmode = mode;
7817 compose->targetinfo = NULL;
7818 compose->replyinfo = NULL;
7819 compose->fwdinfo = NULL;
7821 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7822 g_str_equal, (GDestroyNotify) g_free, NULL);
7824 compose->replyto = NULL;
7826 compose->bcc = NULL;
7827 compose->followup_to = NULL;
7829 compose->ml_post = NULL;
7831 compose->inreplyto = NULL;
7832 compose->references = NULL;
7833 compose->msgid = NULL;
7834 compose->boundary = NULL;
7836 compose->autowrap = prefs_common.autowrap;
7837 compose->autoindent = prefs_common.auto_indent;
7838 compose->use_signing = FALSE;
7839 compose->use_encryption = FALSE;
7840 compose->privacy_system = NULL;
7842 compose->modified = FALSE;
7844 compose->return_receipt = FALSE;
7846 compose->to_list = NULL;
7847 compose->newsgroup_list = NULL;
7849 compose->undostruct = undostruct;
7851 compose->sig_str = NULL;
7853 compose->exteditor_file = NULL;
7854 compose->exteditor_pid = -1;
7855 compose->exteditor_tag = -1;
7856 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7859 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7860 if (mode != COMPOSE_REDIRECT) {
7861 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7862 strcmp(prefs_common.dictionary, "")) {
7863 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7864 prefs_common.alt_dictionary,
7865 conv_get_locale_charset_str(),
7866 prefs_common.misspelled_col,
7867 prefs_common.check_while_typing,
7868 prefs_common.recheck_when_changing_dict,
7869 prefs_common.use_alternate,
7870 prefs_common.use_both_dicts,
7871 GTK_TEXT_VIEW(text),
7872 GTK_WINDOW(compose->window),
7873 compose_dict_changed,
7874 compose_spell_menu_changed,
7877 alertpanel_error(_("Spell checker could not "
7879 gtkaspell_checkers_strerror());
7880 gtkaspell_checkers_reset_error();
7882 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7886 compose->gtkaspell = gtkaspell;
7887 compose_spell_menu_changed(compose);
7888 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7891 compose_select_account(compose, account, TRUE);
7893 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7894 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7896 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7897 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7899 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7900 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7902 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7903 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7905 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7906 if (account->protocol != A_NNTP)
7907 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7908 prefs_common_translated_header_name("To:"));
7910 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7911 prefs_common_translated_header_name("Newsgroups:"));
7913 #ifndef USE_NEW_ADDRBOOK
7914 addressbook_set_target_compose(compose);
7916 if (mode != COMPOSE_REDIRECT)
7917 compose_set_template_menu(compose);
7919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7922 compose_list = g_list_append(compose_list, compose);
7924 if (!prefs_common.show_ruler)
7925 gtk_widget_hide(ruler_hbox);
7927 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7930 compose->priority = PRIORITY_NORMAL;
7931 compose_update_priority_menu_item(compose);
7933 compose_set_out_encoding(compose);
7936 compose_update_actions_menu(compose);
7938 /* Privacy Systems menu */
7939 compose_update_privacy_systems_menu(compose);
7941 activate_privacy_system(compose, account, TRUE);
7942 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7944 gtk_widget_realize(window);
7946 gtk_widget_show(window);
7952 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7957 GtkWidget *optmenubox;
7960 GtkWidget *from_name = NULL;
7961 #if !(GTK_CHECK_VERSION(2,12,0))
7962 GtkTooltips *tips = compose->tooltips;
7965 gint num = 0, def_menu = 0;
7967 accounts = account_get_list();
7968 cm_return_val_if_fail(accounts != NULL, NULL);
7970 optmenubox = gtk_event_box_new();
7971 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7972 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7974 hbox = gtk_hbox_new(FALSE, 6);
7975 from_name = gtk_entry_new();
7977 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7978 G_CALLBACK(compose_grab_focus_cb), compose);
7980 for (; accounts != NULL; accounts = accounts->next, num++) {
7981 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7982 gchar *name, *from = NULL;
7984 if (ac == compose->account) def_menu = num;
7986 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7989 if (ac == compose->account) {
7990 if (ac->name && *ac->name) {
7992 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7993 from = g_strdup_printf("%s <%s>",
7995 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7997 from = g_strdup_printf("%s",
7999 gtk_entry_set_text(GTK_ENTRY(from_name), from);
8002 COMBOBOX_ADD(menu, name, ac->account_id);
8007 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
8009 g_signal_connect(G_OBJECT(optmenu), "changed",
8010 G_CALLBACK(account_activated),
8012 g_signal_connect(G_OBJECT(from_name), "populate-popup",
8013 G_CALLBACK(compose_entry_popup_extend),
8016 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
8017 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
8019 CLAWS_SET_TIP(optmenubox,
8020 _("Account to use for this email"));
8021 CLAWS_SET_TIP(from_name,
8022 _("Sender address to be used"));
8024 compose->account_combo = optmenu;
8025 compose->from_name = from_name;
8030 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8032 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8033 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8034 Compose *compose = (Compose *) data;
8036 compose->priority = value;
8040 static void compose_reply_change_mode(Compose *compose,
8043 gboolean was_modified = compose->modified;
8045 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8047 cm_return_if_fail(compose->replyinfo != NULL);
8049 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8051 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8053 if (action == COMPOSE_REPLY_TO_ALL)
8055 if (action == COMPOSE_REPLY_TO_SENDER)
8057 if (action == COMPOSE_REPLY_TO_LIST)
8060 compose_remove_header_entries(compose);
8061 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8062 if (compose->account->set_autocc && compose->account->auto_cc)
8063 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8065 if (compose->account->set_autobcc && compose->account->auto_bcc)
8066 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8068 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8069 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8070 compose_show_first_last_header(compose, TRUE);
8071 compose->modified = was_modified;
8072 compose_set_title(compose);
8075 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8077 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8078 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8079 Compose *compose = (Compose *) data;
8082 compose_reply_change_mode(compose, value);
8085 static void compose_update_priority_menu_item(Compose * compose)
8087 GtkWidget *menuitem = NULL;
8088 switch (compose->priority) {
8089 case PRIORITY_HIGHEST:
8090 menuitem = gtk_ui_manager_get_widget
8091 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8094 menuitem = gtk_ui_manager_get_widget
8095 (compose->ui_manager, "/Menu/Options/Priority/High");
8097 case PRIORITY_NORMAL:
8098 menuitem = gtk_ui_manager_get_widget
8099 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8102 menuitem = gtk_ui_manager_get_widget
8103 (compose->ui_manager, "/Menu/Options/Priority/Low");
8105 case PRIORITY_LOWEST:
8106 menuitem = gtk_ui_manager_get_widget
8107 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8110 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8113 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8115 Compose *compose = (Compose *) data;
8117 gboolean can_sign = FALSE, can_encrypt = FALSE;
8119 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8121 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8124 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8125 g_free(compose->privacy_system);
8126 compose->privacy_system = NULL;
8127 if (systemid != NULL) {
8128 compose->privacy_system = g_strdup(systemid);
8130 can_sign = privacy_system_can_sign(systemid);
8131 can_encrypt = privacy_system_can_encrypt(systemid);
8134 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8136 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8137 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8140 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8142 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8143 GtkWidget *menuitem = NULL;
8144 GList *children, *amenu;
8145 gboolean can_sign = FALSE, can_encrypt = FALSE;
8146 gboolean found = FALSE;
8148 if (compose->privacy_system != NULL) {
8150 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8151 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8152 cm_return_if_fail(menuitem != NULL);
8154 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8157 while (amenu != NULL) {
8158 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8159 if (systemid != NULL) {
8160 if (strcmp(systemid, compose->privacy_system) == 0 &&
8161 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8162 menuitem = GTK_WIDGET(amenu->data);
8164 can_sign = privacy_system_can_sign(systemid);
8165 can_encrypt = privacy_system_can_encrypt(systemid);
8169 } else if (strlen(compose->privacy_system) == 0 &&
8170 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8171 menuitem = GTK_WIDGET(amenu->data);
8174 can_encrypt = FALSE;
8179 amenu = amenu->next;
8181 g_list_free(children);
8182 if (menuitem != NULL)
8183 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8185 if (warn && !found && strlen(compose->privacy_system)) {
8186 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8187 "will not be able to sign or encrypt this message."),
8188 compose->privacy_system);
8192 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8193 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8196 static void compose_set_out_encoding(Compose *compose)
8198 CharSet out_encoding;
8199 const gchar *branch = NULL;
8200 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8202 switch(out_encoding) {
8203 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8204 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8205 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8206 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8207 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8208 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8209 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8210 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8211 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8212 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8213 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8214 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8215 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8216 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8217 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8218 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8219 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8220 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8221 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8222 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8223 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8224 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8225 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8226 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8227 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8228 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8229 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8230 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8231 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8232 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8233 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8234 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8235 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8237 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8240 static void compose_set_template_menu(Compose *compose)
8242 GSList *tmpl_list, *cur;
8246 tmpl_list = template_get_config();
8248 menu = gtk_menu_new();
8250 gtk_menu_set_accel_group (GTK_MENU (menu),
8251 gtk_ui_manager_get_accel_group(compose->ui_manager));
8252 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8253 Template *tmpl = (Template *)cur->data;
8254 gchar *accel_path = NULL;
8255 item = gtk_menu_item_new_with_label(tmpl->name);
8256 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8257 g_signal_connect(G_OBJECT(item), "activate",
8258 G_CALLBACK(compose_template_activate_cb),
8260 g_object_set_data(G_OBJECT(item), "template", tmpl);
8261 gtk_widget_show(item);
8262 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8263 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8267 gtk_widget_show(menu);
8268 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8271 void compose_update_actions_menu(Compose *compose)
8273 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8276 static void compose_update_privacy_systems_menu(Compose *compose)
8278 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8279 GSList *systems, *cur;
8281 GtkWidget *system_none;
8283 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8284 GtkWidget *privacy_menu = gtk_menu_new();
8286 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8287 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8289 g_signal_connect(G_OBJECT(system_none), "activate",
8290 G_CALLBACK(compose_set_privacy_system_cb), compose);
8292 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8293 gtk_widget_show(system_none);
8295 systems = privacy_get_system_ids();
8296 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8297 gchar *systemid = cur->data;
8299 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8300 widget = gtk_radio_menu_item_new_with_label(group,
8301 privacy_system_get_name(systemid));
8302 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8303 g_strdup(systemid), g_free);
8304 g_signal_connect(G_OBJECT(widget), "activate",
8305 G_CALLBACK(compose_set_privacy_system_cb), compose);
8307 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8308 gtk_widget_show(widget);
8311 g_slist_free(systems);
8312 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8313 gtk_widget_show_all(privacy_menu);
8314 gtk_widget_show_all(privacy_menuitem);
8317 void compose_reflect_prefs_all(void)
8322 for (cur = compose_list; cur != NULL; cur = cur->next) {
8323 compose = (Compose *)cur->data;
8324 compose_set_template_menu(compose);
8328 void compose_reflect_prefs_pixmap_theme(void)
8333 for (cur = compose_list; cur != NULL; cur = cur->next) {
8334 compose = (Compose *)cur->data;
8335 toolbar_update(TOOLBAR_COMPOSE, compose);
8339 static const gchar *compose_quote_char_from_context(Compose *compose)
8341 const gchar *qmark = NULL;
8343 cm_return_val_if_fail(compose != NULL, NULL);
8345 switch (compose->mode) {
8346 /* use forward-specific quote char */
8347 case COMPOSE_FORWARD:
8348 case COMPOSE_FORWARD_AS_ATTACH:
8349 case COMPOSE_FORWARD_INLINE:
8350 if (compose->folder && compose->folder->prefs &&
8351 compose->folder->prefs->forward_with_format)
8352 qmark = compose->folder->prefs->forward_quotemark;
8353 else if (compose->account->forward_with_format)
8354 qmark = compose->account->forward_quotemark;
8356 qmark = prefs_common.fw_quotemark;
8359 /* use reply-specific quote char in all other modes */
8361 if (compose->folder && compose->folder->prefs &&
8362 compose->folder->prefs->reply_with_format)
8363 qmark = compose->folder->prefs->reply_quotemark;
8364 else if (compose->account->reply_with_format)
8365 qmark = compose->account->reply_quotemark;
8367 qmark = prefs_common.quotemark;
8371 if (qmark == NULL || *qmark == '\0')
8377 static void compose_template_apply(Compose *compose, Template *tmpl,
8381 GtkTextBuffer *buffer;
8385 gchar *parsed_str = NULL;
8386 gint cursor_pos = 0;
8387 const gchar *err_msg = _("The body of the template has an error at line %d.");
8390 /* process the body */
8392 text = GTK_TEXT_VIEW(compose->text);
8393 buffer = gtk_text_view_get_buffer(text);
8396 qmark = compose_quote_char_from_context(compose);
8398 if (compose->replyinfo != NULL) {
8401 gtk_text_buffer_set_text(buffer, "", -1);
8402 mark = gtk_text_buffer_get_insert(buffer);
8403 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8405 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8406 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8408 } else if (compose->fwdinfo != NULL) {
8411 gtk_text_buffer_set_text(buffer, "", -1);
8412 mark = gtk_text_buffer_get_insert(buffer);
8413 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8415 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8416 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8419 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8421 GtkTextIter start, end;
8424 gtk_text_buffer_get_start_iter(buffer, &start);
8425 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8426 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8428 /* clear the buffer now */
8430 gtk_text_buffer_set_text(buffer, "", -1);
8432 parsed_str = compose_quote_fmt(compose, dummyinfo,
8433 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8434 procmsg_msginfo_free( dummyinfo );
8440 gtk_text_buffer_set_text(buffer, "", -1);
8441 mark = gtk_text_buffer_get_insert(buffer);
8442 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8445 if (replace && parsed_str && compose->account->auto_sig)
8446 compose_insert_sig(compose, FALSE);
8448 if (replace && parsed_str) {
8449 gtk_text_buffer_get_start_iter(buffer, &iter);
8450 gtk_text_buffer_place_cursor(buffer, &iter);
8454 cursor_pos = quote_fmt_get_cursor_pos();
8455 compose->set_cursor_pos = cursor_pos;
8456 if (cursor_pos == -1)
8458 gtk_text_buffer_get_start_iter(buffer, &iter);
8459 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8460 gtk_text_buffer_place_cursor(buffer, &iter);
8463 /* process the other fields */
8465 compose_template_apply_fields(compose, tmpl);
8466 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8467 quote_fmt_reset_vartable();
8468 compose_changed_cb(NULL, compose);
8471 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8472 gtkaspell_highlight_all(compose->gtkaspell);
8476 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8478 MsgInfo* dummyinfo = NULL;
8479 MsgInfo *msginfo = NULL;
8482 if (compose->replyinfo != NULL)
8483 msginfo = compose->replyinfo;
8484 else if (compose->fwdinfo != NULL)
8485 msginfo = compose->fwdinfo;
8487 dummyinfo = compose_msginfo_new_from_compose(compose);
8488 msginfo = dummyinfo;
8491 if (tmpl->from && *tmpl->from != '\0') {
8493 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8494 compose->gtkaspell);
8496 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8498 quote_fmt_scan_string(tmpl->from);
8501 buf = quote_fmt_get_buffer();
8503 alertpanel_error(_("Template From format error."));
8505 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8509 if (tmpl->to && *tmpl->to != '\0') {
8511 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8512 compose->gtkaspell);
8514 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8516 quote_fmt_scan_string(tmpl->to);
8519 buf = quote_fmt_get_buffer();
8521 alertpanel_error(_("Template To format error."));
8523 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8527 if (tmpl->cc && *tmpl->cc != '\0') {
8529 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8530 compose->gtkaspell);
8532 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8534 quote_fmt_scan_string(tmpl->cc);
8537 buf = quote_fmt_get_buffer();
8539 alertpanel_error(_("Template Cc format error."));
8541 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8545 if (tmpl->bcc && *tmpl->bcc != '\0') {
8547 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8548 compose->gtkaspell);
8550 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8552 quote_fmt_scan_string(tmpl->bcc);
8555 buf = quote_fmt_get_buffer();
8557 alertpanel_error(_("Template Bcc format error."));
8559 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8563 /* process the subject */
8564 if (tmpl->subject && *tmpl->subject != '\0') {
8566 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8567 compose->gtkaspell);
8569 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8571 quote_fmt_scan_string(tmpl->subject);
8574 buf = quote_fmt_get_buffer();
8576 alertpanel_error(_("Template subject format error."));
8578 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8582 procmsg_msginfo_free( dummyinfo );
8585 static void compose_destroy(Compose *compose)
8587 GtkAllocation allocation;
8588 GtkTextBuffer *buffer;
8589 GtkClipboard *clipboard;
8591 compose_list = g_list_remove(compose_list, compose);
8593 if (compose->updating) {
8594 debug_print("danger, not destroying anything now\n");
8595 compose->deferred_destroy = TRUE;
8598 /* NOTE: address_completion_end() does nothing with the window
8599 * however this may change. */
8600 address_completion_end(compose->window);
8602 slist_free_strings_full(compose->to_list);
8603 slist_free_strings_full(compose->newsgroup_list);
8604 slist_free_strings_full(compose->header_list);
8606 slist_free_strings_full(extra_headers);
8607 extra_headers = NULL;
8609 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8611 g_hash_table_destroy(compose->email_hashtable);
8613 procmsg_msginfo_free(compose->targetinfo);
8614 procmsg_msginfo_free(compose->replyinfo);
8615 procmsg_msginfo_free(compose->fwdinfo);
8617 g_free(compose->replyto);
8618 g_free(compose->cc);
8619 g_free(compose->bcc);
8620 g_free(compose->newsgroups);
8621 g_free(compose->followup_to);
8623 g_free(compose->ml_post);
8625 g_free(compose->inreplyto);
8626 g_free(compose->references);
8627 g_free(compose->msgid);
8628 g_free(compose->boundary);
8630 g_free(compose->redirect_filename);
8631 if (compose->undostruct)
8632 undo_destroy(compose->undostruct);
8634 g_free(compose->sig_str);
8636 g_free(compose->exteditor_file);
8638 g_free(compose->orig_charset);
8640 g_free(compose->privacy_system);
8642 #ifndef USE_NEW_ADDRBOOK
8643 if (addressbook_get_target_compose() == compose)
8644 addressbook_set_target_compose(NULL);
8647 if (compose->gtkaspell) {
8648 gtkaspell_delete(compose->gtkaspell);
8649 compose->gtkaspell = NULL;
8653 if (!compose->batch) {
8654 gtk_widget_get_allocation(compose->window, &allocation);
8655 prefs_common.compose_width = allocation.width;
8656 prefs_common.compose_height = allocation.height;
8659 if (!gtk_widget_get_parent(compose->paned))
8660 gtk_widget_destroy(compose->paned);
8661 gtk_widget_destroy(compose->popupmenu);
8663 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8664 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8665 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8667 gtk_widget_destroy(compose->window);
8668 toolbar_destroy(compose->toolbar);
8669 g_free(compose->toolbar);
8670 cm_mutex_free(compose->mutex);
8674 static void compose_attach_info_free(AttachInfo *ainfo)
8676 g_free(ainfo->file);
8677 g_free(ainfo->content_type);
8678 g_free(ainfo->name);
8679 g_free(ainfo->charset);
8683 static void compose_attach_update_label(Compose *compose)
8688 GtkTreeModel *model;
8693 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8694 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8695 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8699 while(gtk_tree_model_iter_next(model, &iter))
8702 text = g_strdup_printf("(%d)", i);
8703 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8707 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8709 Compose *compose = (Compose *)data;
8710 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8711 GtkTreeSelection *selection;
8713 GtkTreeModel *model;
8715 selection = gtk_tree_view_get_selection(tree_view);
8716 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8721 for (cur = sel; cur != NULL; cur = cur->next) {
8722 GtkTreePath *path = cur->data;
8723 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8726 gtk_tree_path_free(path);
8729 for (cur = sel; cur != NULL; cur = cur->next) {
8730 GtkTreeRowReference *ref = cur->data;
8731 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8734 if (gtk_tree_model_get_iter(model, &iter, path))
8735 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8737 gtk_tree_path_free(path);
8738 gtk_tree_row_reference_free(ref);
8742 compose_attach_update_label(compose);
8745 static struct _AttachProperty
8748 GtkWidget *mimetype_entry;
8749 GtkWidget *encoding_optmenu;
8750 GtkWidget *path_entry;
8751 GtkWidget *filename_entry;
8753 GtkWidget *cancel_btn;
8756 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8758 gtk_tree_path_free((GtkTreePath *)ptr);
8761 static void compose_attach_property(GtkAction *action, gpointer data)
8763 Compose *compose = (Compose *)data;
8764 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8766 GtkComboBox *optmenu;
8767 GtkTreeSelection *selection;
8769 GtkTreeModel *model;
8772 static gboolean cancelled;
8774 /* only if one selected */
8775 selection = gtk_tree_view_get_selection(tree_view);
8776 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8779 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8783 path = (GtkTreePath *) sel->data;
8784 gtk_tree_model_get_iter(model, &iter, path);
8785 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8788 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8794 if (!attach_prop.window)
8795 compose_attach_property_create(&cancelled);
8796 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8797 gtk_widget_grab_focus(attach_prop.ok_btn);
8798 gtk_widget_show(attach_prop.window);
8799 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8800 GTK_WINDOW(compose->window));
8802 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8803 if (ainfo->encoding == ENC_UNKNOWN)
8804 combobox_select_by_data(optmenu, ENC_BASE64);
8806 combobox_select_by_data(optmenu, ainfo->encoding);
8808 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8809 ainfo->content_type ? ainfo->content_type : "");
8810 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8811 ainfo->file ? ainfo->file : "");
8812 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8813 ainfo->name ? ainfo->name : "");
8816 const gchar *entry_text;
8818 gchar *cnttype = NULL;
8825 gtk_widget_hide(attach_prop.window);
8826 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8831 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8832 if (*entry_text != '\0') {
8835 text = g_strstrip(g_strdup(entry_text));
8836 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8837 cnttype = g_strdup(text);
8840 alertpanel_error(_("Invalid MIME type."));
8846 ainfo->encoding = combobox_get_active_data(optmenu);
8848 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8849 if (*entry_text != '\0') {
8850 if (is_file_exist(entry_text) &&
8851 (size = get_file_size(entry_text)) > 0)
8852 file = g_strdup(entry_text);
8855 (_("File doesn't exist or is empty."));
8861 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8862 if (*entry_text != '\0') {
8863 g_free(ainfo->name);
8864 ainfo->name = g_strdup(entry_text);
8868 g_free(ainfo->content_type);
8869 ainfo->content_type = cnttype;
8872 g_free(ainfo->file);
8876 ainfo->size = (goffset)size;
8878 /* update tree store */
8879 text = to_human_readable(ainfo->size);
8880 gtk_tree_model_get_iter(model, &iter, path);
8881 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8882 COL_MIMETYPE, ainfo->content_type,
8884 COL_NAME, ainfo->name,
8885 COL_CHARSET, ainfo->charset,
8891 gtk_tree_path_free(path);
8894 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8896 label = gtk_label_new(str); \
8897 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8898 GTK_FILL, 0, 0, 0); \
8899 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8901 entry = gtk_entry_new(); \
8902 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8903 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8906 static void compose_attach_property_create(gboolean *cancelled)
8912 GtkWidget *mimetype_entry;
8915 GtkListStore *optmenu_menu;
8916 GtkWidget *path_entry;
8917 GtkWidget *filename_entry;
8920 GtkWidget *cancel_btn;
8921 GList *mime_type_list, *strlist;
8924 debug_print("Creating attach_property window...\n");
8926 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8927 gtk_widget_set_size_request(window, 480, -1);
8928 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8929 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8930 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8931 g_signal_connect(G_OBJECT(window), "delete_event",
8932 G_CALLBACK(attach_property_delete_event),
8934 g_signal_connect(G_OBJECT(window), "key_press_event",
8935 G_CALLBACK(attach_property_key_pressed),
8938 vbox = gtk_vbox_new(FALSE, 8);
8939 gtk_container_add(GTK_CONTAINER(window), vbox);
8941 table = gtk_table_new(4, 2, FALSE);
8942 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8943 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8944 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8946 label = gtk_label_new(_("MIME type"));
8947 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8949 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8950 #if !GTK_CHECK_VERSION(2, 24, 0)
8951 mimetype_entry = gtk_combo_box_entry_new_text();
8953 mimetype_entry = gtk_combo_box_text_new_with_entry();
8955 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8956 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8958 /* stuff with list */
8959 mime_type_list = procmime_get_mime_type_list();
8961 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8962 MimeType *type = (MimeType *) mime_type_list->data;
8965 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8967 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8970 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8971 (GCompareFunc)strcmp2);
8974 for (mime_type_list = strlist; mime_type_list != NULL;
8975 mime_type_list = mime_type_list->next) {
8976 #if !GTK_CHECK_VERSION(2, 24, 0)
8977 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8979 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8981 g_free(mime_type_list->data);
8983 g_list_free(strlist);
8984 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8985 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8987 label = gtk_label_new(_("Encoding"));
8988 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8990 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8992 hbox = gtk_hbox_new(FALSE, 0);
8993 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8994 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8996 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8997 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8999 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
9000 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
9001 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
9002 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
9003 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
9005 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
9007 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
9008 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
9010 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
9011 &ok_btn, GTK_STOCK_OK,
9013 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
9014 gtk_widget_grab_default(ok_btn);
9016 g_signal_connect(G_OBJECT(ok_btn), "clicked",
9017 G_CALLBACK(attach_property_ok),
9019 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
9020 G_CALLBACK(attach_property_cancel),
9023 gtk_widget_show_all(vbox);
9025 attach_prop.window = window;
9026 attach_prop.mimetype_entry = mimetype_entry;
9027 attach_prop.encoding_optmenu = optmenu;
9028 attach_prop.path_entry = path_entry;
9029 attach_prop.filename_entry = filename_entry;
9030 attach_prop.ok_btn = ok_btn;
9031 attach_prop.cancel_btn = cancel_btn;
9034 #undef SET_LABEL_AND_ENTRY
9036 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9042 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9048 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9049 gboolean *cancelled)
9057 static gboolean attach_property_key_pressed(GtkWidget *widget,
9059 gboolean *cancelled)
9061 if (event && event->keyval == GDK_KEY_Escape) {
9065 if (event && event->keyval == GDK_KEY_Return) {
9073 static void compose_exec_ext_editor(Compose *compose)
9080 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9081 G_DIR_SEPARATOR, compose);
9083 if (pipe(pipe_fds) < 0) {
9089 if ((pid = fork()) < 0) {
9096 /* close the write side of the pipe */
9099 compose->exteditor_file = g_strdup(tmp);
9100 compose->exteditor_pid = pid;
9102 compose_set_ext_editor_sensitive(compose, FALSE);
9105 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9107 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9109 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9113 } else { /* process-monitoring process */
9119 /* close the read side of the pipe */
9122 if (compose_write_body_to_file(compose, tmp) < 0) {
9123 fd_write_all(pipe_fds[1], "2\n", 2);
9127 pid_ed = compose_exec_ext_editor_real(tmp);
9129 fd_write_all(pipe_fds[1], "1\n", 2);
9133 /* wait until editor is terminated */
9134 waitpid(pid_ed, NULL, 0);
9136 fd_write_all(pipe_fds[1], "0\n", 2);
9143 #endif /* G_OS_UNIX */
9147 static gint compose_exec_ext_editor_real(const gchar *file)
9154 cm_return_val_if_fail(file != NULL, -1);
9156 if ((pid = fork()) < 0) {
9161 if (pid != 0) return pid;
9163 /* grandchild process */
9165 if (setpgid(0, getppid()))
9168 if (prefs_common_get_ext_editor_cmd() &&
9169 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9170 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9171 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9173 if (prefs_common_get_ext_editor_cmd())
9174 g_warning("External editor command-line is invalid: '%s'\n",
9175 prefs_common_get_ext_editor_cmd());
9176 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9179 cmdline = strsplit_with_quote(buf, " ", 1024);
9180 execvp(cmdline[0], cmdline);
9183 g_strfreev(cmdline);
9188 static gboolean compose_ext_editor_kill(Compose *compose)
9190 pid_t pgid = compose->exteditor_pid * -1;
9193 ret = kill(pgid, 0);
9195 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9199 msg = g_strdup_printf
9200 (_("The external editor is still working.\n"
9201 "Force terminating the process?\n"
9202 "process group id: %d"), -pgid);
9203 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9204 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9208 if (val == G_ALERTALTERNATE) {
9209 g_source_remove(compose->exteditor_tag);
9210 g_io_channel_shutdown(compose->exteditor_ch,
9212 g_io_channel_unref(compose->exteditor_ch);
9214 if (kill(pgid, SIGTERM) < 0) perror("kill");
9215 waitpid(compose->exteditor_pid, NULL, 0);
9217 g_warning("Terminated process group id: %d", -pgid);
9218 g_warning("Temporary file: %s",
9219 compose->exteditor_file);
9221 compose_set_ext_editor_sensitive(compose, TRUE);
9223 g_free(compose->exteditor_file);
9224 compose->exteditor_file = NULL;
9225 compose->exteditor_pid = -1;
9226 compose->exteditor_ch = NULL;
9227 compose->exteditor_tag = -1;
9235 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9239 Compose *compose = (Compose *)data;
9242 debug_print("Compose: input from monitoring process\n");
9244 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9246 g_io_channel_shutdown(source, FALSE, NULL);
9247 g_io_channel_unref(source);
9249 waitpid(compose->exteditor_pid, NULL, 0);
9251 if (buf[0] == '0') { /* success */
9252 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9253 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9255 gtk_text_buffer_set_text(buffer, "", -1);
9256 compose_insert_file(compose, compose->exteditor_file);
9257 compose_changed_cb(NULL, compose);
9258 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9260 if (claws_unlink(compose->exteditor_file) < 0)
9261 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9262 } else if (buf[0] == '1') { /* failed */
9263 g_warning("Couldn't exec external editor\n");
9264 if (claws_unlink(compose->exteditor_file) < 0)
9265 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9266 } else if (buf[0] == '2') {
9267 g_warning("Couldn't write to file\n");
9268 } else if (buf[0] == '3') {
9269 g_warning("Pipe read failed\n");
9272 compose_set_ext_editor_sensitive(compose, TRUE);
9274 g_free(compose->exteditor_file);
9275 compose->exteditor_file = NULL;
9276 compose->exteditor_pid = -1;
9277 compose->exteditor_ch = NULL;
9278 compose->exteditor_tag = -1;
9283 static void compose_set_ext_editor_sensitive(Compose *compose,
9286 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9287 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9288 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9289 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9290 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9294 gtk_widget_set_sensitive(compose->text, sensitive);
9295 if (compose->toolbar->send_btn)
9296 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9297 if (compose->toolbar->sendl_btn)
9298 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9299 if (compose->toolbar->draft_btn)
9300 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9301 if (compose->toolbar->insert_btn)
9302 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9303 if (compose->toolbar->sig_btn)
9304 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9305 if (compose->toolbar->exteditor_btn)
9306 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9307 if (compose->toolbar->linewrap_current_btn)
9308 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9309 if (compose->toolbar->linewrap_all_btn)
9310 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9312 #endif /* G_OS_UNIX */
9315 * compose_undo_state_changed:
9317 * Change the sensivity of the menuentries undo and redo
9319 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9320 gint redo_state, gpointer data)
9322 Compose *compose = (Compose *)data;
9324 switch (undo_state) {
9325 case UNDO_STATE_TRUE:
9326 if (!undostruct->undo_state) {
9327 undostruct->undo_state = TRUE;
9328 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9331 case UNDO_STATE_FALSE:
9332 if (undostruct->undo_state) {
9333 undostruct->undo_state = FALSE;
9334 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9337 case UNDO_STATE_UNCHANGED:
9339 case UNDO_STATE_REFRESH:
9340 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9343 g_warning("Undo state not recognized");
9347 switch (redo_state) {
9348 case UNDO_STATE_TRUE:
9349 if (!undostruct->redo_state) {
9350 undostruct->redo_state = TRUE;
9351 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9354 case UNDO_STATE_FALSE:
9355 if (undostruct->redo_state) {
9356 undostruct->redo_state = FALSE;
9357 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9360 case UNDO_STATE_UNCHANGED:
9362 case UNDO_STATE_REFRESH:
9363 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9366 g_warning("Redo state not recognized");
9371 /* callback functions */
9373 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9374 GtkAllocation *allocation,
9377 prefs_common.compose_notebook_height = allocation->height;
9380 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9381 * includes "non-client" (windows-izm) in calculation, so this calculation
9382 * may not be accurate.
9384 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9385 GtkAllocation *allocation,
9386 GtkSHRuler *shruler)
9388 if (prefs_common.show_ruler) {
9389 gint char_width = 0, char_height = 0;
9390 gint line_width_in_chars;
9392 gtkut_get_font_size(GTK_WIDGET(widget),
9393 &char_width, &char_height);
9394 line_width_in_chars =
9395 (allocation->width - allocation->x) / char_width;
9397 /* got the maximum */
9398 gtk_shruler_set_range(GTK_SHRULER(shruler),
9399 0.0, line_width_in_chars, 0);
9408 ComposePrefType type;
9409 gboolean entry_marked;
9412 static void account_activated(GtkComboBox *optmenu, gpointer data)
9414 Compose *compose = (Compose *)data;
9417 gchar *folderidentifier;
9418 gint account_id = 0;
9421 GSList *list, *saved_list = NULL;
9422 HeaderEntryState *state;
9423 GtkRcStyle *style = NULL;
9424 #if !GTK_CHECK_VERSION(3, 0, 0)
9425 static GdkColor yellow;
9426 static gboolean color_set = FALSE;
9428 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9431 /* Get ID of active account in the combo box */
9432 menu = gtk_combo_box_get_model(optmenu);
9433 gtk_combo_box_get_active_iter(optmenu, &iter);
9434 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9436 ac = account_find_from_id(account_id);
9437 cm_return_if_fail(ac != NULL);
9439 if (ac != compose->account) {
9440 compose_select_account(compose, ac, FALSE);
9442 for (list = compose->header_list; list; list = list->next) {
9443 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9445 if (hentry->type == PREF_ACCOUNT || !list->next) {
9446 compose_destroy_headerentry(compose, hentry);
9450 state = g_malloc0(sizeof(HeaderEntryState));
9451 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9452 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9453 state->entry = gtk_editable_get_chars(
9454 GTK_EDITABLE(hentry->entry), 0, -1);
9455 state->type = hentry->type;
9457 #if !GTK_CHECK_VERSION(3, 0, 0)
9459 gdk_color_parse("#f5f6be", &yellow);
9460 color_set = gdk_colormap_alloc_color(
9461 gdk_colormap_get_system(),
9462 &yellow, FALSE, TRUE);
9466 style = gtk_widget_get_modifier_style(hentry->entry);
9467 state->entry_marked = gdk_color_equal(&yellow,
9468 &style->base[GTK_STATE_NORMAL]);
9470 saved_list = g_slist_append(saved_list, state);
9471 compose_destroy_headerentry(compose, hentry);
9474 compose->header_last = NULL;
9475 g_slist_free(compose->header_list);
9476 compose->header_list = NULL;
9477 compose->header_nextrow = 1;
9478 compose_create_header_entry(compose);
9480 if (ac->set_autocc && ac->auto_cc)
9481 compose_entry_append(compose, ac->auto_cc,
9482 COMPOSE_CC, PREF_ACCOUNT);
9484 if (ac->set_autobcc && ac->auto_bcc)
9485 compose_entry_append(compose, ac->auto_bcc,
9486 COMPOSE_BCC, PREF_ACCOUNT);
9488 if (ac->set_autoreplyto && ac->auto_replyto)
9489 compose_entry_append(compose, ac->auto_replyto,
9490 COMPOSE_REPLYTO, PREF_ACCOUNT);
9492 for (list = saved_list; list; list = list->next) {
9493 state = (HeaderEntryState *) list->data;
9495 compose_add_header_entry(compose, state->header,
9496 state->entry, state->type);
9497 if (state->entry_marked)
9498 compose_entry_mark_default_to(compose, state->entry);
9500 g_free(state->header);
9501 g_free(state->entry);
9504 g_slist_free(saved_list);
9506 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9507 (ac->protocol == A_NNTP) ?
9508 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9511 /* Set message save folder */
9512 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9513 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9515 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9516 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9518 compose_set_save_to(compose, NULL);
9519 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9520 folderidentifier = folder_item_get_identifier(account_get_special_folder
9521 (compose->account, F_OUTBOX));
9522 compose_set_save_to(compose, folderidentifier);
9523 g_free(folderidentifier);
9527 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9528 GtkTreeViewColumn *column, Compose *compose)
9530 compose_attach_property(NULL, compose);
9533 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9536 Compose *compose = (Compose *)data;
9537 GtkTreeSelection *attach_selection;
9538 gint attach_nr_selected;
9540 if (!event) return FALSE;
9542 if (event->button == 3) {
9543 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9544 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9546 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9547 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9549 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9550 NULL, NULL, event->button, event->time);
9557 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9560 Compose *compose = (Compose *)data;
9562 if (!event) return FALSE;
9564 switch (event->keyval) {
9565 case GDK_KEY_Delete:
9566 compose_attach_remove_selected(NULL, compose);
9572 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9574 toolbar_comp_set_sensitive(compose, allow);
9575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9578 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9580 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9581 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9582 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9584 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9588 static void compose_send_cb(GtkAction *action, gpointer data)
9590 Compose *compose = (Compose *)data;
9592 if (prefs_common.work_offline &&
9593 !inc_offline_should_override(TRUE,
9594 _("Claws Mail needs network access in order "
9595 "to send this email.")))
9598 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9599 g_source_remove(compose->draft_timeout_tag);
9600 compose->draft_timeout_tag = -1;
9603 compose_send(compose);
9606 static void compose_send_later_cb(GtkAction *action, gpointer data)
9608 Compose *compose = (Compose *)data;
9612 compose_allow_user_actions(compose, FALSE);
9613 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9614 compose_allow_user_actions(compose, TRUE);
9618 compose_close(compose);
9619 } else if (val == -1) {
9620 alertpanel_error(_("Could not queue message."));
9621 } else if (val == -2) {
9622 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9623 } else if (val == -3) {
9624 if (privacy_peek_error())
9625 alertpanel_error(_("Could not queue message for sending:\n\n"
9626 "Signature failed: %s"), privacy_get_error());
9627 } else if (val == -4) {
9628 alertpanel_error(_("Could not queue message for sending:\n\n"
9629 "Charset conversion failed."));
9630 } else if (val == -5) {
9631 alertpanel_error(_("Could not queue message for sending:\n\n"
9632 "Couldn't get recipient encryption key."));
9633 } else if (val == -6) {
9636 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9639 #define DRAFTED_AT_EXIT "drafted_at_exit"
9640 static void compose_register_draft(MsgInfo *info)
9642 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9643 DRAFTED_AT_EXIT, NULL);
9644 FILE *fp = g_fopen(filepath, "ab");
9647 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9655 gboolean compose_draft (gpointer data, guint action)
9657 Compose *compose = (Compose *)data;
9662 MsgFlags flag = {0, 0};
9663 static gboolean lock = FALSE;
9664 MsgInfo *newmsginfo;
9666 gboolean target_locked = FALSE;
9667 gboolean err = FALSE;
9669 if (lock) return FALSE;
9671 if (compose->sending)
9674 draft = account_get_special_folder(compose->account, F_DRAFT);
9675 cm_return_val_if_fail(draft != NULL, FALSE);
9677 if (!g_mutex_trylock(compose->mutex)) {
9678 /* we don't want to lock the mutex once it's available,
9679 * because as the only other part of compose.c locking
9680 * it is compose_close - which means once unlocked,
9681 * the compose struct will be freed */
9682 debug_print("couldn't lock mutex, probably sending\n");
9688 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9689 G_DIR_SEPARATOR, compose);
9690 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9691 FILE_OP_ERROR(tmp, "fopen");
9695 /* chmod for security */
9696 if (change_file_mode_rw(fp, tmp) < 0) {
9697 FILE_OP_ERROR(tmp, "chmod");
9698 g_warning("can't change file mode\n");
9701 /* Save draft infos */
9702 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9703 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9705 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9706 gchar *savefolderid;
9708 savefolderid = compose_get_save_to(compose);
9709 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9710 g_free(savefolderid);
9712 if (compose->return_receipt) {
9713 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9715 if (compose->privacy_system) {
9716 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9717 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9718 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9721 /* Message-ID of message replying to */
9722 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9725 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9726 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9729 /* Message-ID of message forwarding to */
9730 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9733 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9734 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9738 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9739 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9741 sheaders = compose_get_manual_headers_info(compose);
9742 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9745 /* end of headers */
9746 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9753 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9757 if (fclose(fp) == EOF) {
9761 if (compose->targetinfo) {
9762 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9763 flag.perm_flags = target_locked?MSG_LOCKED:0;
9765 flag.tmp_flags = MSG_DRAFT;
9767 folder_item_scan(draft);
9768 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9769 MsgInfo *tmpinfo = NULL;
9770 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9771 if (compose->msgid) {
9772 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9775 msgnum = tmpinfo->msgnum;
9776 procmsg_msginfo_free(tmpinfo);
9777 debug_print("got draft msgnum %d from scanning\n", msgnum);
9779 debug_print("didn't get draft msgnum after scanning\n");
9782 debug_print("got draft msgnum %d from adding\n", msgnum);
9788 if (action != COMPOSE_AUTO_SAVE) {
9789 if (action != COMPOSE_DRAFT_FOR_EXIT)
9790 alertpanel_error(_("Could not save draft."));
9793 gtkut_window_popup(compose->window);
9794 val = alertpanel_full(_("Could not save draft"),
9795 _("Could not save draft.\n"
9796 "Do you want to cancel exit or discard this email?"),
9797 _("_Cancel exit"), _("_Discard email"), NULL,
9798 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9799 if (val == G_ALERTALTERNATE) {
9801 g_mutex_unlock(compose->mutex); /* must be done before closing */
9802 compose_close(compose);
9806 g_mutex_unlock(compose->mutex); /* must be done before closing */
9815 if (compose->mode == COMPOSE_REEDIT) {
9816 compose_remove_reedit_target(compose, TRUE);
9819 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9822 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9824 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9826 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9827 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9828 procmsg_msginfo_set_flags(newmsginfo, 0,
9829 MSG_HAS_ATTACHMENT);
9831 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9832 compose_register_draft(newmsginfo);
9834 procmsg_msginfo_free(newmsginfo);
9837 folder_item_scan(draft);
9839 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9841 g_mutex_unlock(compose->mutex); /* must be done before closing */
9842 compose_close(compose);
9848 path = folder_item_fetch_msg(draft, msgnum);
9850 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9853 if (g_stat(path, &s) < 0) {
9854 FILE_OP_ERROR(path, "stat");
9860 procmsg_msginfo_free(compose->targetinfo);
9861 compose->targetinfo = procmsg_msginfo_new();
9862 compose->targetinfo->msgnum = msgnum;
9863 compose->targetinfo->size = (goffset)s.st_size;
9864 compose->targetinfo->mtime = s.st_mtime;
9865 compose->targetinfo->folder = draft;
9867 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9868 compose->mode = COMPOSE_REEDIT;
9870 if (action == COMPOSE_AUTO_SAVE) {
9871 compose->autosaved_draft = compose->targetinfo;
9873 compose->modified = FALSE;
9874 compose_set_title(compose);
9878 g_mutex_unlock(compose->mutex);
9882 void compose_clear_exit_drafts(void)
9884 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9885 DRAFTED_AT_EXIT, NULL);
9886 if (is_file_exist(filepath))
9887 claws_unlink(filepath);
9892 void compose_reopen_exit_drafts(void)
9894 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9895 DRAFTED_AT_EXIT, NULL);
9896 FILE *fp = g_fopen(filepath, "rb");
9900 while (fgets(buf, sizeof(buf), fp)) {
9901 gchar **parts = g_strsplit(buf, "\t", 2);
9902 const gchar *folder = parts[0];
9903 int msgnum = parts[1] ? atoi(parts[1]):-1;
9905 if (folder && *folder && msgnum > -1) {
9906 FolderItem *item = folder_find_item_from_identifier(folder);
9907 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9909 compose_reedit(info, FALSE);
9916 compose_clear_exit_drafts();
9919 static void compose_save_cb(GtkAction *action, gpointer data)
9921 Compose *compose = (Compose *)data;
9922 compose_draft(compose, COMPOSE_KEEP_EDITING);
9923 compose->rmode = COMPOSE_REEDIT;
9926 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9928 if (compose && file_list) {
9931 for ( tmp = file_list; tmp; tmp = tmp->next) {
9932 gchar *file = (gchar *) tmp->data;
9933 gchar *utf8_filename = conv_filename_to_utf8(file);
9934 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9935 compose_changed_cb(NULL, compose);
9940 g_free(utf8_filename);
9945 static void compose_attach_cb(GtkAction *action, gpointer data)
9947 Compose *compose = (Compose *)data;
9950 if (compose->redirect_filename != NULL)
9953 /* Set focus_window properly, in case we were called via popup menu,
9954 * which unsets it (via focus_out_event callback on compose window). */
9955 manage_window_focus_in(compose->window, NULL, NULL);
9957 file_list = filesel_select_multiple_files_open(_("Select file"));
9960 compose_attach_from_list(compose, file_list, TRUE);
9961 g_list_free(file_list);
9965 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9967 Compose *compose = (Compose *)data;
9969 gint files_inserted = 0;
9971 file_list = filesel_select_multiple_files_open(_("Select file"));
9976 for ( tmp = file_list; tmp; tmp = tmp->next) {
9977 gchar *file = (gchar *) tmp->data;
9978 gchar *filedup = g_strdup(file);
9979 gchar *shortfile = g_path_get_basename(filedup);
9980 ComposeInsertResult res;
9981 /* insert the file if the file is short or if the user confirmed that
9982 he/she wants to insert the large file */
9983 res = compose_insert_file(compose, file);
9984 if (res == COMPOSE_INSERT_READ_ERROR) {
9985 alertpanel_error(_("File '%s' could not be read."), shortfile);
9986 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9987 alertpanel_error(_("File '%s' contained invalid characters\n"
9988 "for the current encoding, insertion may be incorrect."),
9990 } else if (res == COMPOSE_INSERT_SUCCESS)
9997 g_list_free(file_list);
10001 if (files_inserted > 0 && compose->gtkaspell &&
10002 compose->gtkaspell->check_while_typing)
10003 gtkaspell_highlight_all(compose->gtkaspell);
10007 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
10009 Compose *compose = (Compose *)data;
10011 compose_insert_sig(compose, FALSE);
10014 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
10018 Compose *compose = (Compose *)data;
10020 gtkut_widget_get_uposition(widget, &x, &y);
10021 if (!compose->batch) {
10022 prefs_common.compose_x = x;
10023 prefs_common.compose_y = y;
10025 if (compose->sending || compose->updating)
10027 compose_close_cb(NULL, compose);
10031 void compose_close_toolbar(Compose *compose)
10033 compose_close_cb(NULL, compose);
10036 static void compose_close_cb(GtkAction *action, gpointer data)
10038 Compose *compose = (Compose *)data;
10042 if (compose->exteditor_tag != -1) {
10043 if (!compose_ext_editor_kill(compose))
10048 if (compose->modified) {
10049 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10050 if (!g_mutex_trylock(compose->mutex)) {
10051 /* we don't want to lock the mutex once it's available,
10052 * because as the only other part of compose.c locking
10053 * it is compose_close - which means once unlocked,
10054 * the compose struct will be freed */
10055 debug_print("couldn't lock mutex, probably sending\n");
10059 val = alertpanel(_("Discard message"),
10060 _("This message has been modified. Discard it?"),
10061 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10063 val = alertpanel(_("Save changes"),
10064 _("This message has been modified. Save the latest changes?"),
10065 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10067 g_mutex_unlock(compose->mutex);
10069 case G_ALERTDEFAULT:
10070 if (prefs_common.autosave && !reedit)
10071 compose_remove_draft(compose);
10073 case G_ALERTALTERNATE:
10074 compose_draft(data, COMPOSE_QUIT_EDITING);
10081 compose_close(compose);
10084 static void compose_print_cb(GtkAction *action, gpointer data)
10086 Compose *compose = (Compose *) data;
10088 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10089 if (compose->targetinfo)
10090 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10093 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10095 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10096 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10097 Compose *compose = (Compose *) data;
10100 compose->out_encoding = (CharSet)value;
10103 static void compose_address_cb(GtkAction *action, gpointer data)
10105 Compose *compose = (Compose *)data;
10107 #ifndef USE_NEW_ADDRBOOK
10108 addressbook_open(compose);
10110 GError* error = NULL;
10111 addressbook_connect_signals(compose);
10112 addressbook_dbus_open(TRUE, &error);
10114 g_warning("%s", error->message);
10115 g_error_free(error);
10120 static void about_show_cb(GtkAction *action, gpointer data)
10125 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10127 Compose *compose = (Compose *)data;
10132 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10133 cm_return_if_fail(tmpl != NULL);
10135 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10137 val = alertpanel(_("Apply template"), msg,
10138 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10141 if (val == G_ALERTDEFAULT)
10142 compose_template_apply(compose, tmpl, TRUE);
10143 else if (val == G_ALERTALTERNATE)
10144 compose_template_apply(compose, tmpl, FALSE);
10147 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10149 Compose *compose = (Compose *)data;
10151 compose_exec_ext_editor(compose);
10154 static void compose_undo_cb(GtkAction *action, gpointer data)
10156 Compose *compose = (Compose *)data;
10157 gboolean prev_autowrap = compose->autowrap;
10159 compose->autowrap = FALSE;
10160 undo_undo(compose->undostruct);
10161 compose->autowrap = prev_autowrap;
10164 static void compose_redo_cb(GtkAction *action, gpointer data)
10166 Compose *compose = (Compose *)data;
10167 gboolean prev_autowrap = compose->autowrap;
10169 compose->autowrap = FALSE;
10170 undo_redo(compose->undostruct);
10171 compose->autowrap = prev_autowrap;
10174 static void entry_cut_clipboard(GtkWidget *entry)
10176 if (GTK_IS_EDITABLE(entry))
10177 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10178 else if (GTK_IS_TEXT_VIEW(entry))
10179 gtk_text_buffer_cut_clipboard(
10180 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10181 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10185 static void entry_copy_clipboard(GtkWidget *entry)
10187 if (GTK_IS_EDITABLE(entry))
10188 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10189 else if (GTK_IS_TEXT_VIEW(entry))
10190 gtk_text_buffer_copy_clipboard(
10191 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10192 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10195 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10196 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10198 if (GTK_IS_TEXT_VIEW(entry)) {
10199 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10200 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10201 GtkTextIter start_iter, end_iter;
10203 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10205 if (contents == NULL)
10208 /* we shouldn't delete the selection when middle-click-pasting, or we
10209 * can't mid-click-paste our own selection */
10210 if (clip != GDK_SELECTION_PRIMARY) {
10211 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10212 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10215 if (insert_place == NULL) {
10216 /* if insert_place isn't specified, insert at the cursor.
10217 * used for Ctrl-V pasting */
10218 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10219 start = gtk_text_iter_get_offset(&start_iter);
10220 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10222 /* if insert_place is specified, paste here.
10223 * used for mid-click-pasting */
10224 start = gtk_text_iter_get_offset(insert_place);
10225 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10226 if (prefs_common.primary_paste_unselects)
10227 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10231 /* paste unwrapped: mark the paste so it's not wrapped later */
10232 end = start + strlen(contents);
10233 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10234 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10235 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10236 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10237 /* rewrap paragraph now (after a mid-click-paste) */
10238 mark_start = gtk_text_buffer_get_insert(buffer);
10239 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10240 gtk_text_iter_backward_char(&start_iter);
10241 compose_beautify_paragraph(compose, &start_iter, TRUE);
10243 } else if (GTK_IS_EDITABLE(entry))
10244 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10246 compose->modified = TRUE;
10249 static void entry_allsel(GtkWidget *entry)
10251 if (GTK_IS_EDITABLE(entry))
10252 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10253 else if (GTK_IS_TEXT_VIEW(entry)) {
10254 GtkTextIter startiter, enditer;
10255 GtkTextBuffer *textbuf;
10257 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10258 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10259 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10261 gtk_text_buffer_move_mark_by_name(textbuf,
10262 "selection_bound", &startiter);
10263 gtk_text_buffer_move_mark_by_name(textbuf,
10264 "insert", &enditer);
10268 static void compose_cut_cb(GtkAction *action, gpointer data)
10270 Compose *compose = (Compose *)data;
10271 if (compose->focused_editable
10272 #ifndef GENERIC_UMPC
10273 && gtk_widget_has_focus(compose->focused_editable)
10276 entry_cut_clipboard(compose->focused_editable);
10279 static void compose_copy_cb(GtkAction *action, gpointer data)
10281 Compose *compose = (Compose *)data;
10282 if (compose->focused_editable
10283 #ifndef GENERIC_UMPC
10284 && gtk_widget_has_focus(compose->focused_editable)
10287 entry_copy_clipboard(compose->focused_editable);
10290 static void compose_paste_cb(GtkAction *action, gpointer data)
10292 Compose *compose = (Compose *)data;
10293 gint prev_autowrap;
10294 GtkTextBuffer *buffer;
10296 if (compose->focused_editable &&
10297 #ifndef GENERIC_UMPC
10298 gtk_widget_has_focus(compose->focused_editable)
10301 entry_paste_clipboard(compose, compose->focused_editable,
10302 prefs_common.linewrap_pastes,
10303 GDK_SELECTION_CLIPBOARD, NULL);
10308 #ifndef GENERIC_UMPC
10309 gtk_widget_has_focus(compose->text) &&
10311 compose->gtkaspell &&
10312 compose->gtkaspell->check_while_typing)
10313 gtkaspell_highlight_all(compose->gtkaspell);
10317 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10319 Compose *compose = (Compose *)data;
10320 gint wrap_quote = prefs_common.linewrap_quote;
10321 if (compose->focused_editable
10322 #ifndef GENERIC_UMPC
10323 && gtk_widget_has_focus(compose->focused_editable)
10326 /* let text_insert() (called directly or at a later time
10327 * after the gtk_editable_paste_clipboard) know that
10328 * text is to be inserted as a quotation. implemented
10329 * by using a simple refcount... */
10330 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10331 G_OBJECT(compose->focused_editable),
10332 "paste_as_quotation"));
10333 g_object_set_data(G_OBJECT(compose->focused_editable),
10334 "paste_as_quotation",
10335 GINT_TO_POINTER(paste_as_quotation + 1));
10336 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10337 entry_paste_clipboard(compose, compose->focused_editable,
10338 prefs_common.linewrap_pastes,
10339 GDK_SELECTION_CLIPBOARD, NULL);
10340 prefs_common.linewrap_quote = wrap_quote;
10344 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10346 Compose *compose = (Compose *)data;
10347 gint prev_autowrap;
10348 GtkTextBuffer *buffer;
10350 if (compose->focused_editable
10351 #ifndef GENERIC_UMPC
10352 && gtk_widget_has_focus(compose->focused_editable)
10355 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10356 GDK_SELECTION_CLIPBOARD, NULL);
10361 #ifndef GENERIC_UMPC
10362 gtk_widget_has_focus(compose->text) &&
10364 compose->gtkaspell &&
10365 compose->gtkaspell->check_while_typing)
10366 gtkaspell_highlight_all(compose->gtkaspell);
10370 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10372 Compose *compose = (Compose *)data;
10373 gint prev_autowrap;
10374 GtkTextBuffer *buffer;
10376 if (compose->focused_editable
10377 #ifndef GENERIC_UMPC
10378 && gtk_widget_has_focus(compose->focused_editable)
10381 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10382 GDK_SELECTION_CLIPBOARD, NULL);
10387 #ifndef GENERIC_UMPC
10388 gtk_widget_has_focus(compose->text) &&
10390 compose->gtkaspell &&
10391 compose->gtkaspell->check_while_typing)
10392 gtkaspell_highlight_all(compose->gtkaspell);
10396 static void compose_allsel_cb(GtkAction *action, gpointer data)
10398 Compose *compose = (Compose *)data;
10399 if (compose->focused_editable
10400 #ifndef GENERIC_UMPC
10401 && gtk_widget_has_focus(compose->focused_editable)
10404 entry_allsel(compose->focused_editable);
10407 static void textview_move_beginning_of_line (GtkTextView *text)
10409 GtkTextBuffer *buffer;
10413 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10415 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10416 mark = gtk_text_buffer_get_insert(buffer);
10417 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10418 gtk_text_iter_set_line_offset(&ins, 0);
10419 gtk_text_buffer_place_cursor(buffer, &ins);
10422 static void textview_move_forward_character (GtkTextView *text)
10424 GtkTextBuffer *buffer;
10428 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10430 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10431 mark = gtk_text_buffer_get_insert(buffer);
10432 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10433 if (gtk_text_iter_forward_cursor_position(&ins))
10434 gtk_text_buffer_place_cursor(buffer, &ins);
10437 static void textview_move_backward_character (GtkTextView *text)
10439 GtkTextBuffer *buffer;
10443 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10445 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10446 mark = gtk_text_buffer_get_insert(buffer);
10447 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10448 if (gtk_text_iter_backward_cursor_position(&ins))
10449 gtk_text_buffer_place_cursor(buffer, &ins);
10452 static void textview_move_forward_word (GtkTextView *text)
10454 GtkTextBuffer *buffer;
10459 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10461 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10462 mark = gtk_text_buffer_get_insert(buffer);
10463 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10464 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10465 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10466 gtk_text_iter_backward_word_start(&ins);
10467 gtk_text_buffer_place_cursor(buffer, &ins);
10471 static void textview_move_backward_word (GtkTextView *text)
10473 GtkTextBuffer *buffer;
10477 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10479 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10480 mark = gtk_text_buffer_get_insert(buffer);
10481 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10482 if (gtk_text_iter_backward_word_starts(&ins, 1))
10483 gtk_text_buffer_place_cursor(buffer, &ins);
10486 static void textview_move_end_of_line (GtkTextView *text)
10488 GtkTextBuffer *buffer;
10492 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10494 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10495 mark = gtk_text_buffer_get_insert(buffer);
10496 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10497 if (gtk_text_iter_forward_to_line_end(&ins))
10498 gtk_text_buffer_place_cursor(buffer, &ins);
10501 static void textview_move_next_line (GtkTextView *text)
10503 GtkTextBuffer *buffer;
10508 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10510 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10511 mark = gtk_text_buffer_get_insert(buffer);
10512 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10513 offset = gtk_text_iter_get_line_offset(&ins);
10514 if (gtk_text_iter_forward_line(&ins)) {
10515 gtk_text_iter_set_line_offset(&ins, offset);
10516 gtk_text_buffer_place_cursor(buffer, &ins);
10520 static void textview_move_previous_line (GtkTextView *text)
10522 GtkTextBuffer *buffer;
10527 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10529 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10530 mark = gtk_text_buffer_get_insert(buffer);
10531 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10532 offset = gtk_text_iter_get_line_offset(&ins);
10533 if (gtk_text_iter_backward_line(&ins)) {
10534 gtk_text_iter_set_line_offset(&ins, offset);
10535 gtk_text_buffer_place_cursor(buffer, &ins);
10539 static void textview_delete_forward_character (GtkTextView *text)
10541 GtkTextBuffer *buffer;
10543 GtkTextIter ins, end_iter;
10545 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10547 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10548 mark = gtk_text_buffer_get_insert(buffer);
10549 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10551 if (gtk_text_iter_forward_char(&end_iter)) {
10552 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10556 static void textview_delete_backward_character (GtkTextView *text)
10558 GtkTextBuffer *buffer;
10560 GtkTextIter ins, end_iter;
10562 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10564 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10565 mark = gtk_text_buffer_get_insert(buffer);
10566 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10568 if (gtk_text_iter_backward_char(&end_iter)) {
10569 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10573 static void textview_delete_forward_word (GtkTextView *text)
10575 GtkTextBuffer *buffer;
10577 GtkTextIter ins, end_iter;
10579 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10581 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10582 mark = gtk_text_buffer_get_insert(buffer);
10583 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10585 if (gtk_text_iter_forward_word_end(&end_iter)) {
10586 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10590 static void textview_delete_backward_word (GtkTextView *text)
10592 GtkTextBuffer *buffer;
10594 GtkTextIter ins, end_iter;
10596 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10598 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10599 mark = gtk_text_buffer_get_insert(buffer);
10600 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10602 if (gtk_text_iter_backward_word_start(&end_iter)) {
10603 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10607 static void textview_delete_line (GtkTextView *text)
10609 GtkTextBuffer *buffer;
10611 GtkTextIter ins, start_iter, end_iter;
10613 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10615 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10616 mark = gtk_text_buffer_get_insert(buffer);
10617 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10620 gtk_text_iter_set_line_offset(&start_iter, 0);
10623 if (gtk_text_iter_ends_line(&end_iter)){
10624 if (!gtk_text_iter_forward_char(&end_iter))
10625 gtk_text_iter_backward_char(&start_iter);
10628 gtk_text_iter_forward_to_line_end(&end_iter);
10629 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10632 static void textview_delete_to_line_end (GtkTextView *text)
10634 GtkTextBuffer *buffer;
10636 GtkTextIter ins, end_iter;
10638 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10640 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10641 mark = gtk_text_buffer_get_insert(buffer);
10642 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10644 if (gtk_text_iter_ends_line(&end_iter))
10645 gtk_text_iter_forward_char(&end_iter);
10647 gtk_text_iter_forward_to_line_end(&end_iter);
10648 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10651 #define DO_ACTION(name, act) { \
10652 if(!strcmp(name, a_name)) { \
10656 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10658 const gchar *a_name = gtk_action_get_name(action);
10659 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10660 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10661 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10662 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10663 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10664 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10665 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10666 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10667 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10668 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10669 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10670 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10671 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10672 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10676 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10678 Compose *compose = (Compose *)data;
10679 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10680 ComposeCallAdvancedAction action = -1;
10682 action = compose_call_advanced_action_from_path(gaction);
10685 void (*do_action) (GtkTextView *text);
10686 } action_table[] = {
10687 {textview_move_beginning_of_line},
10688 {textview_move_forward_character},
10689 {textview_move_backward_character},
10690 {textview_move_forward_word},
10691 {textview_move_backward_word},
10692 {textview_move_end_of_line},
10693 {textview_move_next_line},
10694 {textview_move_previous_line},
10695 {textview_delete_forward_character},
10696 {textview_delete_backward_character},
10697 {textview_delete_forward_word},
10698 {textview_delete_backward_word},
10699 {textview_delete_line},
10700 {textview_delete_to_line_end}
10703 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10705 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10706 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10707 if (action_table[action].do_action)
10708 action_table[action].do_action(text);
10710 g_warning("Not implemented yet.");
10714 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10716 GtkAllocation allocation;
10720 if (GTK_IS_EDITABLE(widget)) {
10721 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10722 gtk_editable_set_position(GTK_EDITABLE(widget),
10725 if ((parent = gtk_widget_get_parent(widget))
10726 && (parent = gtk_widget_get_parent(parent))
10727 && (parent = gtk_widget_get_parent(parent))) {
10728 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10729 gtk_widget_get_allocation(widget, &allocation);
10730 gint y = allocation.y;
10731 gint height = allocation.height;
10732 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10733 (GTK_SCROLLED_WINDOW(parent));
10735 gfloat value = gtk_adjustment_get_value(shown);
10736 gfloat upper = gtk_adjustment_get_upper(shown);
10737 gfloat page_size = gtk_adjustment_get_page_size(shown);
10738 if (y < (int)value) {
10739 gtk_adjustment_set_value(shown, y - 1);
10741 if ((y + height) > ((int)value + (int)page_size)) {
10742 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10743 gtk_adjustment_set_value(shown,
10744 y + height - (int)page_size - 1);
10746 gtk_adjustment_set_value(shown,
10747 (int)upper - (int)page_size - 1);
10754 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10755 compose->focused_editable = widget;
10757 #ifdef GENERIC_UMPC
10758 if (GTK_IS_TEXT_VIEW(widget)
10759 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10760 g_object_ref(compose->notebook);
10761 g_object_ref(compose->edit_vbox);
10762 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10763 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10764 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10765 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10766 g_object_unref(compose->notebook);
10767 g_object_unref(compose->edit_vbox);
10768 g_signal_handlers_block_by_func(G_OBJECT(widget),
10769 G_CALLBACK(compose_grab_focus_cb),
10771 gtk_widget_grab_focus(widget);
10772 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10773 G_CALLBACK(compose_grab_focus_cb),
10775 } else if (!GTK_IS_TEXT_VIEW(widget)
10776 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10777 g_object_ref(compose->notebook);
10778 g_object_ref(compose->edit_vbox);
10779 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10780 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10781 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10782 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10783 g_object_unref(compose->notebook);
10784 g_object_unref(compose->edit_vbox);
10785 g_signal_handlers_block_by_func(G_OBJECT(widget),
10786 G_CALLBACK(compose_grab_focus_cb),
10788 gtk_widget_grab_focus(widget);
10789 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10790 G_CALLBACK(compose_grab_focus_cb),
10796 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10798 compose->modified = TRUE;
10799 // compose_beautify_paragraph(compose, NULL, TRUE);
10800 #ifndef GENERIC_UMPC
10801 compose_set_title(compose);
10805 static void compose_wrap_cb(GtkAction *action, gpointer data)
10807 Compose *compose = (Compose *)data;
10808 compose_beautify_paragraph(compose, NULL, TRUE);
10811 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10813 Compose *compose = (Compose *)data;
10814 compose_wrap_all_full(compose, TRUE);
10817 static void compose_find_cb(GtkAction *action, gpointer data)
10819 Compose *compose = (Compose *)data;
10821 message_search_compose(compose);
10824 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10827 Compose *compose = (Compose *)data;
10828 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10829 if (compose->autowrap)
10830 compose_wrap_all_full(compose, TRUE);
10831 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10834 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10837 Compose *compose = (Compose *)data;
10838 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10841 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10843 Compose *compose = (Compose *)data;
10845 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10848 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10850 Compose *compose = (Compose *)data;
10852 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10855 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10857 g_free(compose->privacy_system);
10859 compose->privacy_system = g_strdup(account->default_privacy_system);
10860 compose_update_privacy_system_menu_item(compose, warn);
10863 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10865 Compose *compose = (Compose *)data;
10867 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10868 gtk_widget_show(compose->ruler_hbox);
10869 prefs_common.show_ruler = TRUE;
10871 gtk_widget_hide(compose->ruler_hbox);
10872 gtk_widget_queue_resize(compose->edit_vbox);
10873 prefs_common.show_ruler = FALSE;
10877 static void compose_attach_drag_received_cb (GtkWidget *widget,
10878 GdkDragContext *context,
10881 GtkSelectionData *data,
10884 gpointer user_data)
10886 Compose *compose = (Compose *)user_data;
10890 type = gtk_selection_data_get_data_type(data);
10891 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10893 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10895 ) && gtk_drag_get_source_widget(context) !=
10896 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10897 list = uri_list_extract_filenames(
10898 (const gchar *)gtk_selection_data_get_data(data));
10899 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10900 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10901 compose_attach_append
10902 (compose, (const gchar *)tmp->data,
10903 utf8_filename, NULL, NULL);
10904 g_free(utf8_filename);
10906 if (list) compose_changed_cb(NULL, compose);
10907 list_free_strings(list);
10909 } else if (gtk_drag_get_source_widget(context)
10910 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10911 /* comes from our summaryview */
10912 SummaryView * summaryview = NULL;
10913 GSList * list = NULL, *cur = NULL;
10915 if (mainwindow_get_mainwindow())
10916 summaryview = mainwindow_get_mainwindow()->summaryview;
10919 list = summary_get_selected_msg_list(summaryview);
10921 for (cur = list; cur; cur = cur->next) {
10922 MsgInfo *msginfo = (MsgInfo *)cur->data;
10923 gchar *file = NULL;
10925 file = procmsg_get_message_file_full(msginfo,
10928 compose_attach_append(compose, (const gchar *)file,
10929 (const gchar *)file, "message/rfc822", NULL);
10933 g_slist_free(list);
10937 static gboolean compose_drag_drop(GtkWidget *widget,
10938 GdkDragContext *drag_context,
10940 guint time, gpointer user_data)
10942 /* not handling this signal makes compose_insert_drag_received_cb
10947 static gboolean completion_set_focus_to_subject
10948 (GtkWidget *widget,
10949 GdkEventKey *event,
10952 cm_return_val_if_fail(compose != NULL, FALSE);
10954 /* make backtab move to subject field */
10955 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10956 gtk_widget_grab_focus(compose->subject_entry);
10962 static void compose_insert_drag_received_cb (GtkWidget *widget,
10963 GdkDragContext *drag_context,
10966 GtkSelectionData *data,
10969 gpointer user_data)
10971 Compose *compose = (Compose *)user_data;
10975 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10977 type = gtk_selection_data_get_data_type(data);
10979 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10981 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10983 AlertValue val = G_ALERTDEFAULT;
10984 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10986 list = uri_list_extract_filenames(ddata);
10987 if (list == NULL && strstr(ddata, "://")) {
10988 /* Assume a list of no files, and data has ://, is a remote link */
10989 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10990 gchar *tmpfile = get_tmp_file();
10991 str_write_to_file(tmpdata, tmpfile);
10993 compose_insert_file(compose, tmpfile);
10994 claws_unlink(tmpfile);
10996 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10997 compose_beautify_paragraph(compose, NULL, TRUE);
11000 switch (prefs_common.compose_dnd_mode) {
11001 case COMPOSE_DND_ASK:
11002 val = alertpanel_full(_("Insert or attach?"),
11003 _("Do you want to insert the contents of the file(s) "
11004 "into the message body, or attach it to the email?"),
11005 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
11006 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
11008 case COMPOSE_DND_INSERT:
11009 val = G_ALERTALTERNATE;
11011 case COMPOSE_DND_ATTACH:
11012 val = G_ALERTOTHER;
11015 /* unexpected case */
11016 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11019 if (val & G_ALERTDISABLE) {
11020 val &= ~G_ALERTDISABLE;
11021 /* remember what action to perform by default, only if we don't click Cancel */
11022 if (val == G_ALERTALTERNATE)
11023 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
11024 else if (val == G_ALERTOTHER)
11025 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
11028 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
11029 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11030 list_free_strings(list);
11033 } else if (val == G_ALERTOTHER) {
11034 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11035 list_free_strings(list);
11040 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11041 compose_insert_file(compose, (const gchar *)tmp->data);
11043 list_free_strings(list);
11045 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11050 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11053 static void compose_header_drag_received_cb (GtkWidget *widget,
11054 GdkDragContext *drag_context,
11057 GtkSelectionData *data,
11060 gpointer user_data)
11062 GtkEditable *entry = (GtkEditable *)user_data;
11063 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11065 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11068 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11069 gchar *decoded=g_new(gchar, strlen(email));
11072 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11073 gtk_editable_delete_text(entry, 0, -1);
11074 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11075 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11079 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11082 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11084 Compose *compose = (Compose *)data;
11086 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11087 compose->return_receipt = TRUE;
11089 compose->return_receipt = FALSE;
11092 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11094 Compose *compose = (Compose *)data;
11096 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11097 compose->remove_references = TRUE;
11099 compose->remove_references = FALSE;
11102 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11103 ComposeHeaderEntry *headerentry)
11105 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11109 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11110 GdkEventKey *event,
11111 ComposeHeaderEntry *headerentry)
11113 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11114 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11115 !(event->state & GDK_MODIFIER_MASK) &&
11116 (event->keyval == GDK_KEY_BackSpace) &&
11117 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11118 gtk_container_remove
11119 (GTK_CONTAINER(headerentry->compose->header_table),
11120 headerentry->combo);
11121 gtk_container_remove
11122 (GTK_CONTAINER(headerentry->compose->header_table),
11123 headerentry->entry);
11124 headerentry->compose->header_list =
11125 g_slist_remove(headerentry->compose->header_list,
11127 g_free(headerentry);
11128 } else if (event->keyval == GDK_KEY_Tab) {
11129 if (headerentry->compose->header_last == headerentry) {
11130 /* Override default next focus, and give it to subject_entry
11131 * instead of notebook tabs
11133 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11134 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11141 static gboolean scroll_postpone(gpointer data)
11143 Compose *compose = (Compose *)data;
11145 cm_return_val_if_fail(!compose->batch, FALSE);
11147 GTK_EVENTS_FLUSH();
11148 compose_show_first_last_header(compose, FALSE);
11152 static void compose_headerentry_changed_cb(GtkWidget *entry,
11153 ComposeHeaderEntry *headerentry)
11155 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11156 compose_create_header_entry(headerentry->compose);
11157 g_signal_handlers_disconnect_matched
11158 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11159 0, 0, NULL, NULL, headerentry);
11161 if (!headerentry->compose->batch)
11162 g_timeout_add(0, scroll_postpone, headerentry->compose);
11166 static gboolean compose_defer_auto_save_draft(Compose *compose)
11168 compose->draft_timeout_tag = -1;
11169 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11173 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11175 GtkAdjustment *vadj;
11177 cm_return_if_fail(compose);
11178 cm_return_if_fail(!compose->batch);
11179 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11180 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11181 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11182 gtk_widget_get_parent(compose->header_table)));
11183 gtk_adjustment_set_value(vadj, (show_first ?
11184 gtk_adjustment_get_lower(vadj) :
11185 (gtk_adjustment_get_upper(vadj) -
11186 gtk_adjustment_get_page_size(vadj))));
11187 gtk_adjustment_changed(vadj);
11190 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11191 const gchar *text, gint len, Compose *compose)
11193 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11194 (G_OBJECT(compose->text), "paste_as_quotation"));
11197 cm_return_if_fail(text != NULL);
11199 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11200 G_CALLBACK(text_inserted),
11202 if (paste_as_quotation) {
11204 const gchar *qmark;
11206 GtkTextIter start_iter;
11209 len = strlen(text);
11211 new_text = g_strndup(text, len);
11213 qmark = compose_quote_char_from_context(compose);
11215 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11216 gtk_text_buffer_place_cursor(buffer, iter);
11218 pos = gtk_text_iter_get_offset(iter);
11220 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11221 _("Quote format error at line %d."));
11222 quote_fmt_reset_vartable();
11224 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11225 GINT_TO_POINTER(paste_as_quotation - 1));
11227 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11228 gtk_text_buffer_place_cursor(buffer, iter);
11229 gtk_text_buffer_delete_mark(buffer, mark);
11231 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11232 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11233 compose_beautify_paragraph(compose, &start_iter, FALSE);
11234 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11235 gtk_text_buffer_delete_mark(buffer, mark);
11237 if (strcmp(text, "\n") || compose->automatic_break
11238 || gtk_text_iter_starts_line(iter)) {
11239 GtkTextIter before_ins;
11240 gtk_text_buffer_insert(buffer, iter, text, len);
11241 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11242 before_ins = *iter;
11243 gtk_text_iter_backward_chars(&before_ins, len);
11244 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11247 /* check if the preceding is just whitespace or quote */
11248 GtkTextIter start_line;
11249 gchar *tmp = NULL, *quote = NULL;
11250 gint quote_len = 0, is_normal = 0;
11251 start_line = *iter;
11252 gtk_text_iter_set_line_offset(&start_line, 0);
11253 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11256 if (*tmp == '\0') {
11259 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11267 gtk_text_buffer_insert(buffer, iter, text, len);
11269 gtk_text_buffer_insert_with_tags_by_name(buffer,
11270 iter, text, len, "no_join", NULL);
11275 if (!paste_as_quotation) {
11276 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11277 compose_beautify_paragraph(compose, iter, FALSE);
11278 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11279 gtk_text_buffer_delete_mark(buffer, mark);
11282 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11283 G_CALLBACK(text_inserted),
11285 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11287 if (prefs_common.autosave &&
11288 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11289 compose->draft_timeout_tag != -2 /* disabled while loading */)
11290 compose->draft_timeout_tag = g_timeout_add
11291 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11295 static void compose_check_all(GtkAction *action, gpointer data)
11297 Compose *compose = (Compose *)data;
11298 if (!compose->gtkaspell)
11301 if (gtk_widget_has_focus(compose->subject_entry))
11302 claws_spell_entry_check_all(
11303 CLAWS_SPELL_ENTRY(compose->subject_entry));
11305 gtkaspell_check_all(compose->gtkaspell);
11308 static void compose_highlight_all(GtkAction *action, gpointer data)
11310 Compose *compose = (Compose *)data;
11311 if (compose->gtkaspell) {
11312 claws_spell_entry_recheck_all(
11313 CLAWS_SPELL_ENTRY(compose->subject_entry));
11314 gtkaspell_highlight_all(compose->gtkaspell);
11318 static void compose_check_backwards(GtkAction *action, gpointer data)
11320 Compose *compose = (Compose *)data;
11321 if (!compose->gtkaspell) {
11322 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11326 if (gtk_widget_has_focus(compose->subject_entry))
11327 claws_spell_entry_check_backwards(
11328 CLAWS_SPELL_ENTRY(compose->subject_entry));
11330 gtkaspell_check_backwards(compose->gtkaspell);
11333 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11335 Compose *compose = (Compose *)data;
11336 if (!compose->gtkaspell) {
11337 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11341 if (gtk_widget_has_focus(compose->subject_entry))
11342 claws_spell_entry_check_forwards_go(
11343 CLAWS_SPELL_ENTRY(compose->subject_entry));
11345 gtkaspell_check_forwards_go(compose->gtkaspell);
11350 *\brief Guess originating forward account from MsgInfo and several
11351 * "common preference" settings. Return NULL if no guess.
11353 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11355 PrefsAccount *account = NULL;
11357 cm_return_val_if_fail(msginfo, NULL);
11358 cm_return_val_if_fail(msginfo->folder, NULL);
11359 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11361 if (msginfo->folder->prefs->enable_default_account)
11362 account = account_find_from_id(msginfo->folder->prefs->default_account);
11365 account = msginfo->folder->folder->account;
11367 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11369 Xstrdup_a(to, msginfo->to, return NULL);
11370 extract_address(to);
11371 account = account_find_from_address(to, FALSE);
11374 if (!account && prefs_common.forward_account_autosel) {
11375 gchar cc[BUFFSIZE];
11376 if (!procheader_get_header_from_msginfo
11377 (msginfo, cc,sizeof cc , "Cc:")) {
11378 gchar *buf = cc + strlen("Cc:");
11379 extract_address(buf);
11380 account = account_find_from_address(buf, FALSE);
11384 if (!account && prefs_common.forward_account_autosel) {
11385 gchar deliveredto[BUFFSIZE];
11386 if (!procheader_get_header_from_msginfo
11387 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11388 gchar *buf = deliveredto + strlen("Delivered-To:");
11389 extract_address(buf);
11390 account = account_find_from_address(buf, FALSE);
11397 gboolean compose_close(Compose *compose)
11401 if (!g_mutex_trylock(compose->mutex)) {
11402 /* we have to wait for the (possibly deferred by auto-save)
11403 * drafting to be done, before destroying the compose under
11405 debug_print("waiting for drafting to finish...\n");
11406 compose_allow_user_actions(compose, FALSE);
11407 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11410 cm_return_val_if_fail(compose, FALSE);
11411 gtkut_widget_get_uposition(compose->window, &x, &y);
11412 if (!compose->batch) {
11413 prefs_common.compose_x = x;
11414 prefs_common.compose_y = y;
11416 g_mutex_unlock(compose->mutex);
11417 compose_destroy(compose);
11422 * Add entry field for each address in list.
11423 * \param compose E-Mail composition object.
11424 * \param listAddress List of (formatted) E-Mail addresses.
11426 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11429 node = listAddress;
11431 addr = ( gchar * ) node->data;
11432 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11433 node = g_list_next( node );
11437 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11438 guint action, gboolean opening_multiple)
11440 gchar *body = NULL;
11441 GSList *new_msglist = NULL;
11442 MsgInfo *tmp_msginfo = NULL;
11443 gboolean originally_enc = FALSE;
11444 gboolean originally_sig = FALSE;
11445 Compose *compose = NULL;
11446 gchar *s_system = NULL;
11448 cm_return_if_fail(msgview != NULL);
11450 cm_return_if_fail(msginfo_list != NULL);
11452 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11453 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11454 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11456 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11457 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11458 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11459 orig_msginfo, mimeinfo);
11460 if (tmp_msginfo != NULL) {
11461 new_msglist = g_slist_append(NULL, tmp_msginfo);
11463 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11464 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11465 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11467 tmp_msginfo->folder = orig_msginfo->folder;
11468 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11469 if (orig_msginfo->tags) {
11470 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11471 tmp_msginfo->folder->tags_dirty = TRUE;
11477 if (!opening_multiple)
11478 body = messageview_get_selection(msgview);
11481 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11482 procmsg_msginfo_free(tmp_msginfo);
11483 g_slist_free(new_msglist);
11485 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11487 if (compose && originally_enc) {
11488 compose_force_encryption(compose, compose->account, FALSE, s_system);
11491 if (compose && originally_sig && compose->account->default_sign_reply) {
11492 compose_force_signing(compose, compose->account, s_system);
11496 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11499 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11502 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11503 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11504 GSList *cur = msginfo_list;
11505 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11506 "messages. Opening the windows "
11507 "could take some time. Do you "
11508 "want to continue?"),
11509 g_slist_length(msginfo_list));
11510 if (g_slist_length(msginfo_list) > 9
11511 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11512 != G_ALERTALTERNATE) {
11517 /* We'll open multiple compose windows */
11518 /* let the WM place the next windows */
11519 compose_force_window_origin = FALSE;
11520 for (; cur; cur = cur->next) {
11522 tmplist.data = cur->data;
11523 tmplist.next = NULL;
11524 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11526 compose_force_window_origin = TRUE;
11528 /* forwarding multiple mails as attachments is done via a
11529 * single compose window */
11530 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11534 void compose_check_for_email_account(Compose *compose)
11536 PrefsAccount *ac = NULL, *curr = NULL;
11542 if (compose->account && compose->account->protocol == A_NNTP) {
11543 ac = account_get_cur_account();
11544 if (ac->protocol == A_NNTP) {
11545 list = account_get_list();
11547 for( ; list != NULL ; list = g_list_next(list)) {
11548 curr = (PrefsAccount *) list->data;
11549 if (curr->protocol != A_NNTP) {
11555 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11560 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11561 const gchar *address)
11563 GSList *msginfo_list = NULL;
11564 gchar *body = messageview_get_selection(msgview);
11567 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11569 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11570 compose_check_for_email_account(compose);
11571 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11572 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11573 compose_reply_set_subject(compose, msginfo);
11576 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11579 void compose_set_position(Compose *compose, gint pos)
11581 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11583 gtkut_text_view_set_position(text, pos);
11586 gboolean compose_search_string(Compose *compose,
11587 const gchar *str, gboolean case_sens)
11589 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11591 return gtkut_text_view_search_string(text, str, case_sens);
11594 gboolean compose_search_string_backward(Compose *compose,
11595 const gchar *str, gboolean case_sens)
11597 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11599 return gtkut_text_view_search_string_backward(text, str, case_sens);
11602 /* allocate a msginfo structure and populate its data from a compose data structure */
11603 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11605 MsgInfo *newmsginfo;
11607 gchar buf[BUFFSIZE];
11609 cm_return_val_if_fail( compose != NULL, NULL );
11611 newmsginfo = procmsg_msginfo_new();
11614 get_rfc822_date(buf, sizeof(buf));
11615 newmsginfo->date = g_strdup(buf);
11618 if (compose->from_name) {
11619 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11620 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11624 if (compose->subject_entry)
11625 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11627 /* to, cc, reply-to, newsgroups */
11628 for (list = compose->header_list; list; list = list->next) {
11629 gchar *header = gtk_editable_get_chars(
11631 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11632 gchar *entry = gtk_editable_get_chars(
11633 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11635 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11636 if ( newmsginfo->to == NULL ) {
11637 newmsginfo->to = g_strdup(entry);
11638 } else if (entry && *entry) {
11639 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11640 g_free(newmsginfo->to);
11641 newmsginfo->to = tmp;
11644 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11645 if ( newmsginfo->cc == NULL ) {
11646 newmsginfo->cc = g_strdup(entry);
11647 } else if (entry && *entry) {
11648 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11649 g_free(newmsginfo->cc);
11650 newmsginfo->cc = tmp;
11653 if ( strcasecmp(header,
11654 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11655 if ( newmsginfo->newsgroups == NULL ) {
11656 newmsginfo->newsgroups = g_strdup(entry);
11657 } else if (entry && *entry) {
11658 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11659 g_free(newmsginfo->newsgroups);
11660 newmsginfo->newsgroups = tmp;
11668 /* other data is unset */
11674 /* update compose's dictionaries from folder dict settings */
11675 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11676 FolderItem *folder_item)
11678 cm_return_if_fail(compose != NULL);
11680 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11681 FolderItemPrefs *prefs = folder_item->prefs;
11683 if (prefs->enable_default_dictionary)
11684 gtkaspell_change_dict(compose->gtkaspell,
11685 prefs->default_dictionary, FALSE);
11686 if (folder_item->prefs->enable_default_alt_dictionary)
11687 gtkaspell_change_alt_dict(compose->gtkaspell,
11688 prefs->default_alt_dictionary);
11689 if (prefs->enable_default_dictionary
11690 || prefs->enable_default_alt_dictionary)
11691 compose_spell_menu_changed(compose);
11696 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11698 Compose *compose = (Compose *)data;
11700 cm_return_if_fail(compose != NULL);
11702 gtk_widget_grab_focus(compose->text);