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) {
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(compose->sending?_("Send"):_("Send later"), message,
5018 GTK_STOCK_CANCEL, button_label, NULL);
5020 if (aval != G_ALERTALTERNATE)
5025 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5031 gint compose_send(Compose *compose)
5034 FolderItem *folder = NULL;
5036 gchar *msgpath = NULL;
5037 gboolean discard_window = FALSE;
5038 gchar *errstr = NULL;
5039 gchar *tmsgid = NULL;
5040 MainWindow *mainwin = mainwindow_get_mainwindow();
5041 gboolean queued_removed = FALSE;
5043 if (prefs_common.send_dialog_invisible
5044 || compose->batch == TRUE)
5045 discard_window = TRUE;
5047 compose_allow_user_actions (compose, FALSE);
5048 compose->sending = TRUE;
5050 if (compose_check_entries(compose, TRUE) == FALSE) {
5051 if (compose->batch) {
5052 gtk_widget_show_all(compose->window);
5058 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5061 if (compose->batch) {
5062 gtk_widget_show_all(compose->window);
5065 alertpanel_error(_("Could not queue message for sending:\n\n"
5066 "Charset conversion failed."));
5067 } else if (val == -5) {
5068 alertpanel_error(_("Could not queue message for sending:\n\n"
5069 "Couldn't get recipient encryption key."));
5070 } else if (val == -6) {
5072 } else if (val == -3) {
5073 if (privacy_peek_error())
5074 alertpanel_error(_("Could not queue message for sending:\n\n"
5075 "Signature failed: %s"), privacy_get_error());
5076 } else if (val == -2 && errno != 0) {
5077 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5079 alertpanel_error(_("Could not queue message for sending."));
5084 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5085 if (discard_window) {
5086 compose->sending = FALSE;
5087 compose_close(compose);
5088 /* No more compose access in the normal codepath
5089 * after this point! */
5094 alertpanel_error(_("The message was queued but could not be "
5095 "sent.\nUse \"Send queued messages\" from "
5096 "the main window to retry."));
5097 if (!discard_window) {
5104 if (msgpath == NULL) {
5105 msgpath = folder_item_fetch_msg(folder, msgnum);
5106 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5109 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5110 claws_unlink(msgpath);
5113 if (!discard_window) {
5115 if (!queued_removed)
5116 folder_item_remove_msg(folder, msgnum);
5117 folder_item_scan(folder);
5119 /* make sure we delete that */
5120 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5122 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5123 folder_item_remove_msg(folder, tmp->msgnum);
5124 procmsg_msginfo_free(tmp);
5131 if (!queued_removed)
5132 folder_item_remove_msg(folder, msgnum);
5133 folder_item_scan(folder);
5135 /* make sure we delete that */
5136 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5138 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5139 folder_item_remove_msg(folder, tmp->msgnum);
5140 procmsg_msginfo_free(tmp);
5143 if (!discard_window) {
5144 compose->sending = FALSE;
5145 compose_allow_user_actions (compose, TRUE);
5146 compose_close(compose);
5150 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5151 "the main window to retry."), errstr);
5154 alertpanel_error_log(_("The message was queued but could not be "
5155 "sent.\nUse \"Send queued messages\" from "
5156 "the main window to retry."));
5158 if (!discard_window) {
5167 toolbar_main_set_sensitive(mainwin);
5168 main_window_set_menu_sensitive(mainwin);
5174 compose_allow_user_actions (compose, TRUE);
5175 compose->sending = FALSE;
5176 compose->modified = TRUE;
5177 toolbar_main_set_sensitive(mainwin);
5178 main_window_set_menu_sensitive(mainwin);
5183 static gboolean compose_use_attach(Compose *compose)
5185 GtkTreeModel *model = gtk_tree_view_get_model
5186 (GTK_TREE_VIEW(compose->attach_clist));
5187 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5190 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5193 gchar buf[BUFFSIZE];
5195 gboolean first_to_address;
5196 gboolean first_cc_address;
5198 ComposeHeaderEntry *headerentry;
5199 const gchar *headerentryname;
5200 const gchar *cc_hdr;
5201 const gchar *to_hdr;
5202 gboolean err = FALSE;
5204 debug_print("Writing redirect header\n");
5206 cc_hdr = prefs_common_translated_header_name("Cc:");
5207 to_hdr = prefs_common_translated_header_name("To:");
5209 first_to_address = TRUE;
5210 for (list = compose->header_list; list; list = list->next) {
5211 headerentry = ((ComposeHeaderEntry *)list->data);
5212 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5214 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5215 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5216 Xstrdup_a(str, entstr, return -1);
5218 if (str[0] != '\0') {
5219 compose_convert_header
5220 (compose, buf, sizeof(buf), str,
5221 strlen("Resent-To") + 2, TRUE);
5223 if (first_to_address) {
5224 err |= (fprintf(fp, "Resent-To: ") < 0);
5225 first_to_address = FALSE;
5227 err |= (fprintf(fp, ",") < 0);
5229 err |= (fprintf(fp, "%s", buf) < 0);
5233 if (!first_to_address) {
5234 err |= (fprintf(fp, "\n") < 0);
5237 first_cc_address = TRUE;
5238 for (list = compose->header_list; list; list = list->next) {
5239 headerentry = ((ComposeHeaderEntry *)list->data);
5240 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5242 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5243 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5244 Xstrdup_a(str, strg, return -1);
5246 if (str[0] != '\0') {
5247 compose_convert_header
5248 (compose, buf, sizeof(buf), str,
5249 strlen("Resent-Cc") + 2, TRUE);
5251 if (first_cc_address) {
5252 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5253 first_cc_address = FALSE;
5255 err |= (fprintf(fp, ",") < 0);
5257 err |= (fprintf(fp, "%s", buf) < 0);
5261 if (!first_cc_address) {
5262 err |= (fprintf(fp, "\n") < 0);
5265 return (err ? -1:0);
5268 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5270 gchar buf[BUFFSIZE];
5272 const gchar *entstr;
5273 /* struct utsname utsbuf; */
5274 gboolean err = FALSE;
5276 cm_return_val_if_fail(fp != NULL, -1);
5277 cm_return_val_if_fail(compose->account != NULL, -1);
5278 cm_return_val_if_fail(compose->account->address != NULL, -1);
5281 get_rfc822_date(buf, sizeof(buf));
5282 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5285 if (compose->account->name && *compose->account->name) {
5286 compose_convert_header
5287 (compose, buf, sizeof(buf), compose->account->name,
5288 strlen("From: "), TRUE);
5289 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5290 buf, compose->account->address) < 0);
5292 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5295 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5296 if (*entstr != '\0') {
5297 Xstrdup_a(str, entstr, return -1);
5300 compose_convert_header(compose, buf, sizeof(buf), str,
5301 strlen("Subject: "), FALSE);
5302 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5306 /* Resent-Message-ID */
5307 if (compose->account->set_domain && compose->account->domain) {
5308 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5309 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5310 g_snprintf(buf, sizeof(buf), "%s",
5311 strchr(compose->account->address, '@') ?
5312 strchr(compose->account->address, '@')+1 :
5313 compose->account->address);
5315 g_snprintf(buf, sizeof(buf), "%s", "");
5318 if (compose->account->gen_msgid) {
5320 if (compose->account->msgid_with_addr) {
5321 addr = compose->account->address;
5323 generate_msgid(buf, sizeof(buf), addr);
5324 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5325 compose->msgid = g_strdup(buf);
5327 compose->msgid = NULL;
5330 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5333 /* separator between header and body */
5334 err |= (fputs("\n", fp) == EOF);
5336 return (err ? -1:0);
5339 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5343 gchar buf[BUFFSIZE];
5345 gboolean skip = FALSE;
5346 gboolean err = FALSE;
5347 gchar *not_included[]={
5348 "Return-Path:", "Delivered-To:", "Received:",
5349 "Subject:", "X-UIDL:", "AF:",
5350 "NF:", "PS:", "SRH:",
5351 "SFN:", "DSR:", "MID:",
5352 "CFG:", "PT:", "S:",
5353 "RQ:", "SSV:", "NSV:",
5354 "SSH:", "R:", "MAID:",
5355 "NAID:", "RMID:", "FMID:",
5356 "SCF:", "RRCPT:", "NG:",
5357 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5358 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5359 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5360 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5361 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5364 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5365 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5369 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5371 for (i = 0; not_included[i] != NULL; i++) {
5372 if (g_ascii_strncasecmp(buf, not_included[i],
5373 strlen(not_included[i])) == 0) {
5380 if (fputs(buf, fdest) == -1)
5383 if (!prefs_common.redirect_keep_from) {
5384 if (g_ascii_strncasecmp(buf, "From:",
5385 strlen("From:")) == 0) {
5386 err |= (fputs(" (by way of ", fdest) == EOF);
5387 if (compose->account->name
5388 && *compose->account->name) {
5389 compose_convert_header
5390 (compose, buf, sizeof(buf),
5391 compose->account->name,
5394 err |= (fprintf(fdest, "%s <%s>",
5396 compose->account->address) < 0);
5398 err |= (fprintf(fdest, "%s",
5399 compose->account->address) < 0);
5400 err |= (fputs(")", fdest) == EOF);
5404 if (fputs("\n", fdest) == -1)
5411 if (compose_redirect_write_headers(compose, fdest))
5414 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5415 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5428 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5430 GtkTextBuffer *buffer;
5431 GtkTextIter start, end;
5434 const gchar *out_codeset;
5435 EncodingType encoding = ENC_UNKNOWN;
5436 MimeInfo *mimemsg, *mimetext;
5438 const gchar *src_codeset = CS_INTERNAL;
5439 gchar *from_addr = NULL;
5440 gchar *from_name = NULL;
5442 if (action == COMPOSE_WRITE_FOR_SEND)
5443 attach_parts = TRUE;
5445 /* create message MimeInfo */
5446 mimemsg = procmime_mimeinfo_new();
5447 mimemsg->type = MIMETYPE_MESSAGE;
5448 mimemsg->subtype = g_strdup("rfc822");
5449 mimemsg->content = MIMECONTENT_MEM;
5450 mimemsg->tmp = TRUE; /* must free content later */
5451 mimemsg->data.mem = compose_get_header(compose);
5453 /* Create text part MimeInfo */
5454 /* get all composed text */
5455 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5456 gtk_text_buffer_get_start_iter(buffer, &start);
5457 gtk_text_buffer_get_end_iter(buffer, &end);
5458 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5460 out_codeset = conv_get_charset_str(compose->out_encoding);
5462 if (!out_codeset && is_ascii_str(chars)) {
5463 out_codeset = CS_US_ASCII;
5464 } else if (prefs_common.outgoing_fallback_to_ascii &&
5465 is_ascii_str(chars)) {
5466 out_codeset = CS_US_ASCII;
5467 encoding = ENC_7BIT;
5471 gchar *test_conv_global_out = NULL;
5472 gchar *test_conv_reply = NULL;
5474 /* automatic mode. be automatic. */
5475 codeconv_set_strict(TRUE);
5477 out_codeset = conv_get_outgoing_charset_str();
5479 debug_print("trying to convert to %s\n", out_codeset);
5480 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5483 if (!test_conv_global_out && compose->orig_charset
5484 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5485 out_codeset = compose->orig_charset;
5486 debug_print("failure; trying to convert to %s\n", out_codeset);
5487 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5490 if (!test_conv_global_out && !test_conv_reply) {
5492 out_codeset = CS_INTERNAL;
5493 debug_print("failure; finally using %s\n", out_codeset);
5495 g_free(test_conv_global_out);
5496 g_free(test_conv_reply);
5497 codeconv_set_strict(FALSE);
5500 if (encoding == ENC_UNKNOWN) {
5501 if (prefs_common.encoding_method == CTE_BASE64)
5502 encoding = ENC_BASE64;
5503 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5504 encoding = ENC_QUOTED_PRINTABLE;
5505 else if (prefs_common.encoding_method == CTE_8BIT)
5506 encoding = ENC_8BIT;
5508 encoding = procmime_get_encoding_for_charset(out_codeset);
5511 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5512 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5514 if (action == COMPOSE_WRITE_FOR_SEND) {
5515 codeconv_set_strict(TRUE);
5516 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5517 codeconv_set_strict(FALSE);
5523 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5524 "to the specified %s charset.\n"
5525 "Send it as %s?"), out_codeset, src_codeset);
5526 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5527 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5530 if (aval != G_ALERTALTERNATE) {
5535 out_codeset = src_codeset;
5541 out_codeset = src_codeset;
5546 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5547 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5548 strstr(buf, "\nFrom ") != NULL) {
5549 encoding = ENC_QUOTED_PRINTABLE;
5553 mimetext = procmime_mimeinfo_new();
5554 mimetext->content = MIMECONTENT_MEM;
5555 mimetext->tmp = TRUE; /* must free content later */
5556 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5557 * and free the data, which we need later. */
5558 mimetext->data.mem = g_strdup(buf);
5559 mimetext->type = MIMETYPE_TEXT;
5560 mimetext->subtype = g_strdup("plain");
5561 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5562 g_strdup(out_codeset));
5564 /* protect trailing spaces when signing message */
5565 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5566 privacy_system_can_sign(compose->privacy_system)) {
5567 encoding = ENC_QUOTED_PRINTABLE;
5570 debug_print("main text: %zd bytes encoded as %s in %d\n",
5571 strlen(buf), out_codeset, encoding);
5573 /* check for line length limit */
5574 if (action == COMPOSE_WRITE_FOR_SEND &&
5575 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5576 check_line_length(buf, 1000, &line) < 0) {
5580 msg = g_strdup_printf
5581 (_("Line %d exceeds the line length limit (998 bytes).\n"
5582 "The contents of the message might be broken on the way to the delivery.\n"
5584 "Send it anyway?"), line + 1);
5585 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5587 if (aval != G_ALERTALTERNATE) {
5593 if (encoding != ENC_UNKNOWN)
5594 procmime_encode_content(mimetext, encoding);
5596 /* append attachment parts */
5597 if (compose_use_attach(compose) && attach_parts) {
5598 MimeInfo *mimempart;
5599 gchar *boundary = NULL;
5600 mimempart = procmime_mimeinfo_new();
5601 mimempart->content = MIMECONTENT_EMPTY;
5602 mimempart->type = MIMETYPE_MULTIPART;
5603 mimempart->subtype = g_strdup("mixed");
5607 boundary = generate_mime_boundary(NULL);
5608 } while (strstr(buf, boundary) != NULL);
5610 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5613 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5615 g_node_append(mimempart->node, mimetext->node);
5616 g_node_append(mimemsg->node, mimempart->node);
5618 if (compose_add_attachments(compose, mimempart) < 0)
5621 g_node_append(mimemsg->node, mimetext->node);
5625 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5626 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5627 /* extract name and address */
5628 if (strstr(spec, " <") && strstr(spec, ">")) {
5629 from_addr = g_strdup(strrchr(spec, '<')+1);
5630 *(strrchr(from_addr, '>')) = '\0';
5631 from_name = g_strdup(spec);
5632 *(strrchr(from_name, '<')) = '\0';
5639 /* sign message if sending */
5640 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5641 privacy_system_can_sign(compose->privacy_system))
5642 if (!privacy_sign(compose->privacy_system, mimemsg,
5643 compose->account, from_addr)) {
5650 procmime_write_mimeinfo(mimemsg, fp);
5652 procmime_mimeinfo_free_all(mimemsg);
5657 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5659 GtkTextBuffer *buffer;
5660 GtkTextIter start, end;
5665 if ((fp = g_fopen(file, "wb")) == NULL) {
5666 FILE_OP_ERROR(file, "fopen");
5670 /* chmod for security */
5671 if (change_file_mode_rw(fp, file) < 0) {
5672 FILE_OP_ERROR(file, "chmod");
5673 g_warning("can't change file mode\n");
5676 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5677 gtk_text_buffer_get_start_iter(buffer, &start);
5678 gtk_text_buffer_get_end_iter(buffer, &end);
5679 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5681 chars = conv_codeset_strdup
5682 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5685 if (!chars) return -1;
5688 len = strlen(chars);
5689 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5690 FILE_OP_ERROR(file, "fwrite");
5699 if (fclose(fp) == EOF) {
5700 FILE_OP_ERROR(file, "fclose");
5707 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5710 MsgInfo *msginfo = compose->targetinfo;
5712 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5713 if (!msginfo) return -1;
5715 if (!force && MSG_IS_LOCKED(msginfo->flags))
5718 item = msginfo->folder;
5719 cm_return_val_if_fail(item != NULL, -1);
5721 if (procmsg_msg_exist(msginfo) &&
5722 (folder_has_parent_of_type(item, F_QUEUE) ||
5723 folder_has_parent_of_type(item, F_DRAFT)
5724 || msginfo == compose->autosaved_draft)) {
5725 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5726 g_warning("can't remove the old message\n");
5729 debug_print("removed reedit target %d\n", msginfo->msgnum);
5736 static void compose_remove_draft(Compose *compose)
5739 MsgInfo *msginfo = compose->targetinfo;
5740 drafts = account_get_special_folder(compose->account, F_DRAFT);
5742 if (procmsg_msg_exist(msginfo)) {
5743 folder_item_remove_msg(drafts, msginfo->msgnum);
5748 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5749 gboolean remove_reedit_target)
5751 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5754 static gboolean compose_warn_encryption(Compose *compose)
5756 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5757 AlertValue val = G_ALERTALTERNATE;
5759 if (warning == NULL)
5762 val = alertpanel_full(_("Encryption warning"), warning,
5763 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5764 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5765 if (val & G_ALERTDISABLE) {
5766 val &= ~G_ALERTDISABLE;
5767 if (val == G_ALERTALTERNATE)
5768 privacy_inhibit_encrypt_warning(compose->privacy_system,
5772 if (val == G_ALERTALTERNATE) {
5779 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5780 gchar **msgpath, gboolean check_subject,
5781 gboolean remove_reedit_target)
5788 PrefsAccount *mailac = NULL, *newsac = NULL;
5789 gboolean err = FALSE;
5791 debug_print("queueing message...\n");
5792 cm_return_val_if_fail(compose->account != NULL, -1);
5794 if (compose_check_entries(compose, check_subject) == FALSE) {
5795 if (compose->batch) {
5796 gtk_widget_show_all(compose->window);
5801 if (!compose->to_list && !compose->newsgroup_list) {
5802 g_warning("can't get recipient list.");
5806 if (compose->to_list) {
5807 if (compose->account->protocol != A_NNTP)
5808 mailac = compose->account;
5809 else if (cur_account && cur_account->protocol != A_NNTP)
5810 mailac = cur_account;
5811 else if (!(mailac = compose_current_mail_account())) {
5812 alertpanel_error(_("No account for sending mails available!"));
5817 if (compose->newsgroup_list) {
5818 if (compose->account->protocol == A_NNTP)
5819 newsac = compose->account;
5821 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5826 /* write queue header */
5827 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5828 G_DIR_SEPARATOR, compose, (guint) rand());
5829 debug_print("queuing to %s\n", tmp);
5830 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5831 FILE_OP_ERROR(tmp, "fopen");
5836 if (change_file_mode_rw(fp, tmp) < 0) {
5837 FILE_OP_ERROR(tmp, "chmod");
5838 g_warning("can't change file mode\n");
5841 /* queueing variables */
5842 err |= (fprintf(fp, "AF:\n") < 0);
5843 err |= (fprintf(fp, "NF:0\n") < 0);
5844 err |= (fprintf(fp, "PS:10\n") < 0);
5845 err |= (fprintf(fp, "SRH:1\n") < 0);
5846 err |= (fprintf(fp, "SFN:\n") < 0);
5847 err |= (fprintf(fp, "DSR:\n") < 0);
5849 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5851 err |= (fprintf(fp, "MID:\n") < 0);
5852 err |= (fprintf(fp, "CFG:\n") < 0);
5853 err |= (fprintf(fp, "PT:0\n") < 0);
5854 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5855 err |= (fprintf(fp, "RQ:\n") < 0);
5857 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5859 err |= (fprintf(fp, "SSV:\n") < 0);
5861 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5863 err |= (fprintf(fp, "NSV:\n") < 0);
5864 err |= (fprintf(fp, "SSH:\n") < 0);
5865 /* write recepient list */
5866 if (compose->to_list) {
5867 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5868 for (cur = compose->to_list->next; cur != NULL;
5870 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5871 err |= (fprintf(fp, "\n") < 0);
5873 /* write newsgroup list */
5874 if (compose->newsgroup_list) {
5875 err |= (fprintf(fp, "NG:") < 0);
5876 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5877 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5878 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5879 err |= (fprintf(fp, "\n") < 0);
5881 /* Sylpheed account IDs */
5883 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5885 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5888 if (compose->privacy_system != NULL) {
5889 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5890 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5891 if (compose->use_encryption) {
5893 if (!compose_warn_encryption(compose)) {
5899 if (mailac && mailac->encrypt_to_self) {
5900 GSList *tmp_list = g_slist_copy(compose->to_list);
5901 tmp_list = g_slist_append(tmp_list, compose->account->address);
5902 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5903 g_slist_free(tmp_list);
5905 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5907 if (encdata != NULL) {
5908 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5909 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5910 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5912 } /* else we finally dont want to encrypt */
5914 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5915 /* and if encdata was null, it means there's been a problem in
5918 g_warning("failed to write queue message");
5928 /* Save copy folder */
5929 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5930 gchar *savefolderid;
5932 savefolderid = compose_get_save_to(compose);
5933 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5934 g_free(savefolderid);
5936 /* Save copy folder */
5937 if (compose->return_receipt) {
5938 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5940 /* Message-ID of message replying to */
5941 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5944 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5945 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5948 /* Message-ID of message forwarding to */
5949 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5952 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5953 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5957 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5958 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5960 /* end of headers */
5961 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5963 if (compose->redirect_filename != NULL) {
5964 if (compose_redirect_write_to_file(compose, fp) < 0) {
5972 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5976 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5980 g_warning("failed to write queue message\n");
5986 if (fclose(fp) == EOF) {
5987 FILE_OP_ERROR(tmp, "fclose");
5993 if (item && *item) {
5996 queue = account_get_special_folder(compose->account, F_QUEUE);
5999 g_warning("can't find queue folder\n");
6004 folder_item_scan(queue);
6005 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6006 g_warning("can't queue the message\n");
6012 if (msgpath == NULL) {
6018 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6019 compose_remove_reedit_target(compose, FALSE);
6022 if ((msgnum != NULL) && (item != NULL)) {
6030 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6033 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6035 struct stat statbuf;
6036 gchar *type, *subtype;
6037 GtkTreeModel *model;
6040 model = gtk_tree_view_get_model(tree_view);
6042 if (!gtk_tree_model_get_iter_first(model, &iter))
6045 gtk_tree_model_get(model, &iter,
6049 if (!is_file_exist(ainfo->file)) {
6050 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6051 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6052 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6054 if (val == G_ALERTDEFAULT) {
6059 mimepart = procmime_mimeinfo_new();
6060 mimepart->content = MIMECONTENT_FILE;
6061 mimepart->data.filename = g_strdup(ainfo->file);
6062 mimepart->tmp = FALSE; /* or we destroy our attachment */
6063 mimepart->offset = 0;
6065 g_stat(ainfo->file, &statbuf);
6066 mimepart->length = statbuf.st_size;
6068 type = g_strdup(ainfo->content_type);
6070 if (!strchr(type, '/')) {
6072 type = g_strdup("application/octet-stream");
6075 subtype = strchr(type, '/') + 1;
6076 *(subtype - 1) = '\0';
6077 mimepart->type = procmime_get_media_type(type);
6078 mimepart->subtype = g_strdup(subtype);
6081 if (mimepart->type == MIMETYPE_MESSAGE &&
6082 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6083 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6084 } else if (mimepart->type == MIMETYPE_TEXT) {
6085 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6086 /* Text parts with no name come from multipart/alternative
6087 * forwards. Make sure the recipient won't look at the
6088 * original HTML part by mistake. */
6089 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6090 ainfo->name = g_strdup_printf(_("Original %s part"),
6094 g_hash_table_insert(mimepart->typeparameters,
6095 g_strdup("charset"), g_strdup(ainfo->charset));
6097 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6098 if (mimepart->type == MIMETYPE_APPLICATION &&
6099 !strcmp2(mimepart->subtype, "octet-stream"))
6100 g_hash_table_insert(mimepart->typeparameters,
6101 g_strdup("name"), g_strdup(ainfo->name));
6102 g_hash_table_insert(mimepart->dispositionparameters,
6103 g_strdup("filename"), g_strdup(ainfo->name));
6104 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6107 if (mimepart->type == MIMETYPE_MESSAGE
6108 || mimepart->type == MIMETYPE_MULTIPART)
6109 ainfo->encoding = ENC_BINARY;
6110 else if (compose->use_signing) {
6111 if (ainfo->encoding == ENC_7BIT)
6112 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6113 else if (ainfo->encoding == ENC_8BIT)
6114 ainfo->encoding = ENC_BASE64;
6119 procmime_encode_content(mimepart, ainfo->encoding);
6121 g_node_append(parent->node, mimepart->node);
6122 } while (gtk_tree_model_iter_next(model, &iter));
6127 #define IS_IN_CUSTOM_HEADER(header) \
6128 (compose->account->add_customhdr && \
6129 custom_header_find(compose->account->customhdr_list, header) != NULL)
6131 static void compose_add_headerfield_from_headerlist(Compose *compose,
6133 const gchar *fieldname,
6134 const gchar *seperator)
6136 gchar *str, *fieldname_w_colon;
6137 gboolean add_field = FALSE;
6139 ComposeHeaderEntry *headerentry;
6140 const gchar *headerentryname;
6141 const gchar *trans_fieldname;
6144 if (IS_IN_CUSTOM_HEADER(fieldname))
6147 debug_print("Adding %s-fields\n", fieldname);
6149 fieldstr = g_string_sized_new(64);
6151 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6152 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6154 for (list = compose->header_list; list; list = list->next) {
6155 headerentry = ((ComposeHeaderEntry *)list->data);
6156 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6158 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6159 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6161 if (str[0] != '\0') {
6163 g_string_append(fieldstr, seperator);
6164 g_string_append(fieldstr, str);
6173 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6174 compose_convert_header
6175 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6176 strlen(fieldname) + 2, TRUE);
6177 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6181 g_free(fieldname_w_colon);
6182 g_string_free(fieldstr, TRUE);
6187 static gchar *compose_get_manual_headers_info(Compose *compose)
6189 GString *sh_header = g_string_new(" ");
6191 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6193 for (list = compose->header_list; list; list = list->next) {
6194 ComposeHeaderEntry *headerentry;
6197 gchar *headername_wcolon;
6198 const gchar *headername_trans;
6200 gboolean standard_header = FALSE;
6202 headerentry = ((ComposeHeaderEntry *)list->data);
6204 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6206 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6211 if (!strstr(tmp, ":")) {
6212 headername_wcolon = g_strconcat(tmp, ":", NULL);
6213 headername = g_strdup(tmp);
6215 headername_wcolon = g_strdup(tmp);
6216 headername = g_strdup(strtok(tmp, ":"));
6220 string = std_headers;
6221 while (*string != NULL) {
6222 headername_trans = prefs_common_translated_header_name(*string);
6223 if (!strcmp(headername_trans, headername_wcolon))
6224 standard_header = TRUE;
6227 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6228 g_string_append_printf(sh_header, "%s ", headername);
6230 g_free(headername_wcolon);
6232 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6233 return g_string_free(sh_header, FALSE);
6236 static gchar *compose_get_header(Compose *compose)
6238 gchar buf[BUFFSIZE];
6239 const gchar *entry_str;
6243 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6245 gchar *from_name = NULL, *from_address = NULL;
6248 cm_return_val_if_fail(compose->account != NULL, NULL);
6249 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6251 header = g_string_sized_new(64);
6254 get_rfc822_date(buf, sizeof(buf));
6255 g_string_append_printf(header, "Date: %s\n", buf);
6259 if (compose->account->name && *compose->account->name) {
6261 QUOTE_IF_REQUIRED(buf, compose->account->name);
6262 tmp = g_strdup_printf("%s <%s>",
6263 buf, compose->account->address);
6265 tmp = g_strdup_printf("%s",
6266 compose->account->address);
6268 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6269 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6271 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6272 from_address = g_strdup(compose->account->address);
6274 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6275 /* extract name and address */
6276 if (strstr(spec, " <") && strstr(spec, ">")) {
6277 from_address = g_strdup(strrchr(spec, '<')+1);
6278 *(strrchr(from_address, '>')) = '\0';
6279 from_name = g_strdup(spec);
6280 *(strrchr(from_name, '<')) = '\0';
6283 from_address = g_strdup(spec);
6290 if (from_name && *from_name) {
6291 compose_convert_header
6292 (compose, buf, sizeof(buf), from_name,
6293 strlen("From: "), TRUE);
6294 QUOTE_IF_REQUIRED(name, buf);
6296 g_string_append_printf(header, "From: %s <%s>\n",
6297 name, from_address);
6299 g_string_append_printf(header, "From: %s\n", from_address);
6302 g_free(from_address);
6305 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6308 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6311 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6315 * If this account is a NNTP account remove Bcc header from
6316 * message body since it otherwise will be publicly shown
6318 if (compose->account->protocol != A_NNTP)
6319 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6322 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6324 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6327 compose_convert_header(compose, buf, sizeof(buf), str,
6328 strlen("Subject: "), FALSE);
6329 g_string_append_printf(header, "Subject: %s\n", buf);
6335 if (compose->account->set_domain && compose->account->domain) {
6336 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6337 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6338 g_snprintf(buf, sizeof(buf), "%s",
6339 strchr(compose->account->address, '@') ?
6340 strchr(compose->account->address, '@')+1 :
6341 compose->account->address);
6343 g_snprintf(buf, sizeof(buf), "%s", "");
6346 if (compose->account->gen_msgid) {
6348 if (compose->account->msgid_with_addr) {
6349 addr = compose->account->address;
6351 generate_msgid(buf, sizeof(buf), addr);
6352 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6353 compose->msgid = g_strdup(buf);
6355 compose->msgid = NULL;
6358 if (compose->remove_references == FALSE) {
6360 if (compose->inreplyto && compose->to_list)
6361 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6364 if (compose->references)
6365 g_string_append_printf(header, "References: %s\n", compose->references);
6369 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6372 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6375 if (compose->account->organization &&
6376 strlen(compose->account->organization) &&
6377 !IS_IN_CUSTOM_HEADER("Organization")) {
6378 compose_convert_header(compose, buf, sizeof(buf),
6379 compose->account->organization,
6380 strlen("Organization: "), FALSE);
6381 g_string_append_printf(header, "Organization: %s\n", buf);
6384 /* Program version and system info */
6385 if (compose->account->gen_xmailer &&
6386 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6387 !compose->newsgroup_list) {
6388 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6390 gtk_major_version, gtk_minor_version, gtk_micro_version,
6393 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6394 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6396 gtk_major_version, gtk_minor_version, gtk_micro_version,
6400 /* custom headers */
6401 if (compose->account->add_customhdr) {
6404 for (cur = compose->account->customhdr_list; cur != NULL;
6406 CustomHeader *chdr = (CustomHeader *)cur->data;
6408 if (custom_header_is_allowed(chdr->name)
6409 && chdr->value != NULL
6410 && *(chdr->value) != '\0') {
6411 compose_convert_header
6412 (compose, buf, sizeof(buf),
6414 strlen(chdr->name) + 2, FALSE);
6415 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6420 /* Automatic Faces and X-Faces */
6421 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6422 g_string_append_printf(header, "X-Face: %s\n", buf);
6424 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6425 g_string_append_printf(header, "X-Face: %s\n", buf);
6427 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6428 g_string_append_printf(header, "Face: %s\n", buf);
6430 else if (get_default_face (buf, sizeof(buf)) == 0) {
6431 g_string_append_printf(header, "Face: %s\n", buf);
6435 switch (compose->priority) {
6436 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6437 "X-Priority: 1 (Highest)\n");
6439 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6440 "X-Priority: 2 (High)\n");
6442 case PRIORITY_NORMAL: break;
6443 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6444 "X-Priority: 4 (Low)\n");
6446 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6447 "X-Priority: 5 (Lowest)\n");
6449 default: debug_print("compose: priority unknown : %d\n",
6453 /* Request Return Receipt */
6454 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6455 if (compose->return_receipt) {
6456 if (compose->account->name
6457 && *compose->account->name) {
6458 compose_convert_header(compose, buf, sizeof(buf),
6459 compose->account->name,
6460 strlen("Disposition-Notification-To: "),
6462 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6464 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6468 /* get special headers */
6469 for (list = compose->header_list; list; list = list->next) {
6470 ComposeHeaderEntry *headerentry;
6473 gchar *headername_wcolon;
6474 const gchar *headername_trans;
6477 gboolean standard_header = FALSE;
6479 headerentry = ((ComposeHeaderEntry *)list->data);
6481 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6483 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6488 if (!strstr(tmp, ":")) {
6489 headername_wcolon = g_strconcat(tmp, ":", NULL);
6490 headername = g_strdup(tmp);
6492 headername_wcolon = g_strdup(tmp);
6493 headername = g_strdup(strtok(tmp, ":"));
6497 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6498 Xstrdup_a(headervalue, entry_str, return NULL);
6499 subst_char(headervalue, '\r', ' ');
6500 subst_char(headervalue, '\n', ' ');
6501 string = std_headers;
6502 while (*string != NULL) {
6503 headername_trans = prefs_common_translated_header_name(*string);
6504 if (!strcmp(headername_trans, headername_wcolon))
6505 standard_header = TRUE;
6508 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6509 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6512 g_free(headername_wcolon);
6516 g_string_free(header, FALSE);
6521 #undef IS_IN_CUSTOM_HEADER
6523 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6524 gint header_len, gboolean addr_field)
6526 gchar *tmpstr = NULL;
6527 const gchar *out_codeset = NULL;
6529 cm_return_if_fail(src != NULL);
6530 cm_return_if_fail(dest != NULL);
6532 if (len < 1) return;
6534 tmpstr = g_strdup(src);
6536 subst_char(tmpstr, '\n', ' ');
6537 subst_char(tmpstr, '\r', ' ');
6540 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6541 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6542 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6547 codeconv_set_strict(TRUE);
6548 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6549 conv_get_charset_str(compose->out_encoding));
6550 codeconv_set_strict(FALSE);
6552 if (!dest || *dest == '\0') {
6553 gchar *test_conv_global_out = NULL;
6554 gchar *test_conv_reply = NULL;
6556 /* automatic mode. be automatic. */
6557 codeconv_set_strict(TRUE);
6559 out_codeset = conv_get_outgoing_charset_str();
6561 debug_print("trying to convert to %s\n", out_codeset);
6562 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6565 if (!test_conv_global_out && compose->orig_charset
6566 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6567 out_codeset = compose->orig_charset;
6568 debug_print("failure; trying to convert to %s\n", out_codeset);
6569 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6572 if (!test_conv_global_out && !test_conv_reply) {
6574 out_codeset = CS_INTERNAL;
6575 debug_print("finally using %s\n", out_codeset);
6577 g_free(test_conv_global_out);
6578 g_free(test_conv_reply);
6579 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6581 codeconv_set_strict(FALSE);
6586 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6590 cm_return_if_fail(user_data != NULL);
6592 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6593 g_strstrip(address);
6594 if (*address != '\0') {
6595 gchar *name = procheader_get_fromname(address);
6596 extract_address(address);
6597 #ifndef USE_NEW_ADDRBOOK
6598 addressbook_add_contact(name, address, NULL, NULL);
6600 debug_print("%s: %s\n", name, address);
6601 if (addressadd_selection(name, address, NULL, NULL)) {
6602 debug_print( "addressbook_add_contact - added\n" );
6609 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6611 GtkWidget *menuitem;
6614 cm_return_if_fail(menu != NULL);
6615 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6617 menuitem = gtk_separator_menu_item_new();
6618 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6619 gtk_widget_show(menuitem);
6621 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6622 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6624 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6625 g_strstrip(address);
6626 if (*address == '\0') {
6627 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6630 g_signal_connect(G_OBJECT(menuitem), "activate",
6631 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6632 gtk_widget_show(menuitem);
6635 void compose_add_extra_header(gchar *header, GtkListStore *model)
6638 if (strcmp(header, "")) {
6639 COMBOBOX_ADD(model, header, COMPOSE_TO);
6643 void compose_add_extra_header_entries(GtkListStore *model)
6647 gchar buf[BUFFSIZE];
6650 if (extra_headers == NULL) {
6651 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6652 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6653 debug_print("extra headers file not found\n");
6654 goto extra_headers_done;
6656 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6657 lastc = strlen(buf) - 1; /* remove trailing control chars */
6658 while (lastc >= 0 && buf[lastc] != ':')
6659 buf[lastc--] = '\0';
6660 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6661 buf[lastc] = '\0'; /* remove trailing : for comparison */
6662 if (custom_header_is_allowed(buf)) {
6664 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6667 g_message("disallowed extra header line: %s\n", buf);
6671 g_message("invalid extra header line: %s\n", buf);
6677 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6678 extra_headers = g_slist_reverse(extra_headers);
6680 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6683 static void compose_create_header_entry(Compose *compose)
6685 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6692 const gchar *header = NULL;
6693 ComposeHeaderEntry *headerentry;
6694 gboolean standard_header = FALSE;
6695 GtkListStore *model;
6697 #if !(GTK_CHECK_VERSION(2,12,0))
6698 GtkTooltips *tips = compose->tooltips;
6701 headerentry = g_new0(ComposeHeaderEntry, 1);
6703 /* Combo box model */
6704 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6705 #if !GTK_CHECK_VERSION(2, 24, 0)
6706 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6708 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6710 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6712 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6714 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6715 COMPOSE_NEWSGROUPS);
6716 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6718 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6719 COMPOSE_FOLLOWUPTO);
6720 compose_add_extra_header_entries(model);
6723 #if GTK_CHECK_VERSION(2, 24, 0)
6724 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6725 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6726 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6727 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6728 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6730 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6731 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6732 G_CALLBACK(compose_grab_focus_cb), compose);
6733 gtk_widget_show(combo);
6736 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6737 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6740 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6741 compose->header_nextrow, compose->header_nextrow+1,
6742 GTK_SHRINK, GTK_FILL, 0, 0);
6743 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6744 const gchar *last_header_entry = gtk_entry_get_text(
6745 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6747 while (*string != NULL) {
6748 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6749 standard_header = TRUE;
6752 if (standard_header)
6753 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6755 if (!compose->header_last || !standard_header) {
6756 switch(compose->account->protocol) {
6758 header = prefs_common_translated_header_name("Newsgroups:");
6761 header = prefs_common_translated_header_name("To:");
6766 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6768 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6769 G_CALLBACK(compose_grab_focus_cb), compose);
6771 /* Entry field with cleanup button */
6772 button = gtk_button_new();
6773 gtk_button_set_image(GTK_BUTTON(button),
6774 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6775 gtk_widget_show(button);
6776 CLAWS_SET_TIP(button,
6777 _("Delete entry contents"));
6778 entry = gtk_entry_new();
6779 gtk_widget_show(entry);
6780 CLAWS_SET_TIP(entry,
6781 _("Use <tab> to autocomplete from addressbook"));
6782 hbox = gtk_hbox_new (FALSE, 0);
6783 gtk_widget_show(hbox);
6784 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6785 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6786 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6787 compose->header_nextrow, compose->header_nextrow+1,
6788 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6790 g_signal_connect(G_OBJECT(entry), "key-press-event",
6791 G_CALLBACK(compose_headerentry_key_press_event_cb),
6793 g_signal_connect(G_OBJECT(entry), "changed",
6794 G_CALLBACK(compose_headerentry_changed_cb),
6796 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6797 G_CALLBACK(compose_grab_focus_cb), compose);
6799 g_signal_connect(G_OBJECT(button), "clicked",
6800 G_CALLBACK(compose_headerentry_button_clicked_cb),
6804 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6805 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6806 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6807 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6808 G_CALLBACK(compose_header_drag_received_cb),
6810 g_signal_connect(G_OBJECT(entry), "drag-drop",
6811 G_CALLBACK(compose_drag_drop),
6813 g_signal_connect(G_OBJECT(entry), "populate-popup",
6814 G_CALLBACK(compose_entry_popup_extend),
6817 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6819 headerentry->compose = compose;
6820 headerentry->combo = combo;
6821 headerentry->entry = entry;
6822 headerentry->button = button;
6823 headerentry->hbox = hbox;
6824 headerentry->headernum = compose->header_nextrow;
6825 headerentry->type = PREF_NONE;
6827 compose->header_nextrow++;
6828 compose->header_last = headerentry;
6829 compose->header_list =
6830 g_slist_append(compose->header_list,
6834 static void compose_add_header_entry(Compose *compose, const gchar *header,
6835 gchar *text, ComposePrefType pref_type)
6837 ComposeHeaderEntry *last_header = compose->header_last;
6838 gchar *tmp = g_strdup(text), *email;
6839 gboolean replyto_hdr;
6841 replyto_hdr = (!strcasecmp(header,
6842 prefs_common_translated_header_name("Reply-To:")) ||
6844 prefs_common_translated_header_name("Followup-To:")) ||
6846 prefs_common_translated_header_name("In-Reply-To:")));
6848 extract_address(tmp);
6849 email = g_utf8_strdown(tmp, -1);
6851 if (replyto_hdr == FALSE &&
6852 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6854 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6855 header, text, (gint) pref_type);
6861 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6862 gtk_entry_set_text(GTK_ENTRY(
6863 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6865 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6866 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6867 last_header->type = pref_type;
6869 if (replyto_hdr == FALSE)
6870 g_hash_table_insert(compose->email_hashtable, email,
6871 GUINT_TO_POINTER(1));
6878 static void compose_destroy_headerentry(Compose *compose,
6879 ComposeHeaderEntry *headerentry)
6881 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6884 extract_address(text);
6885 email = g_utf8_strdown(text, -1);
6886 g_hash_table_remove(compose->email_hashtable, email);
6890 gtk_widget_destroy(headerentry->combo);
6891 gtk_widget_destroy(headerentry->entry);
6892 gtk_widget_destroy(headerentry->button);
6893 gtk_widget_destroy(headerentry->hbox);
6894 g_free(headerentry);
6897 static void compose_remove_header_entries(Compose *compose)
6900 for (list = compose->header_list; list; list = list->next)
6901 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6903 compose->header_last = NULL;
6904 g_slist_free(compose->header_list);
6905 compose->header_list = NULL;
6906 compose->header_nextrow = 1;
6907 compose_create_header_entry(compose);
6910 static GtkWidget *compose_create_header(Compose *compose)
6912 GtkWidget *from_optmenu_hbox;
6913 GtkWidget *header_scrolledwin_main;
6914 GtkWidget *header_table_main;
6915 GtkWidget *header_scrolledwin;
6916 GtkWidget *header_table;
6918 /* parent with account selection and from header */
6919 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6920 gtk_widget_show(header_scrolledwin_main);
6921 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6923 header_table_main = gtk_table_new(2, 2, FALSE);
6924 gtk_widget_show(header_table_main);
6925 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6926 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6927 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6929 from_optmenu_hbox = compose_account_option_menu_create(compose);
6930 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6931 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6933 /* child with header labels and entries */
6934 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6935 gtk_widget_show(header_scrolledwin);
6936 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6938 header_table = gtk_table_new(2, 2, FALSE);
6939 gtk_widget_show(header_table);
6940 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6941 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6942 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6944 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6945 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6947 compose->header_table = header_table;
6948 compose->header_list = NULL;
6949 compose->header_nextrow = 0;
6951 compose_create_header_entry(compose);
6953 compose->table = NULL;
6955 return header_scrolledwin_main;
6958 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6960 Compose *compose = (Compose *)data;
6961 GdkEventButton event;
6964 event.time = gtk_get_current_event_time();
6966 return attach_button_pressed(compose->attach_clist, &event, compose);
6969 static GtkWidget *compose_create_attach(Compose *compose)
6971 GtkWidget *attach_scrwin;
6972 GtkWidget *attach_clist;
6974 GtkListStore *store;
6975 GtkCellRenderer *renderer;
6976 GtkTreeViewColumn *column;
6977 GtkTreeSelection *selection;
6979 /* attachment list */
6980 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6981 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6982 GTK_POLICY_AUTOMATIC,
6983 GTK_POLICY_AUTOMATIC);
6984 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6986 store = gtk_list_store_new(N_ATTACH_COLS,
6992 G_TYPE_AUTO_POINTER,
6994 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6995 (GTK_TREE_MODEL(store)));
6996 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6997 g_object_unref(store);
6999 renderer = gtk_cell_renderer_text_new();
7000 column = gtk_tree_view_column_new_with_attributes
7001 (_("Mime type"), renderer, "text",
7002 COL_MIMETYPE, NULL);
7003 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7005 renderer = gtk_cell_renderer_text_new();
7006 column = gtk_tree_view_column_new_with_attributes
7007 (_("Size"), renderer, "text",
7009 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7011 renderer = gtk_cell_renderer_text_new();
7012 column = gtk_tree_view_column_new_with_attributes
7013 (_("Name"), renderer, "text",
7015 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7017 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7018 prefs_common.use_stripes_everywhere);
7019 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7020 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7022 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7023 G_CALLBACK(attach_selected), compose);
7024 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7025 G_CALLBACK(attach_button_pressed), compose);
7026 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7027 G_CALLBACK(popup_attach_button_pressed), compose);
7028 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7029 G_CALLBACK(attach_key_pressed), compose);
7032 gtk_drag_dest_set(attach_clist,
7033 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7034 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7035 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7036 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7037 G_CALLBACK(compose_attach_drag_received_cb),
7039 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7040 G_CALLBACK(compose_drag_drop),
7043 compose->attach_scrwin = attach_scrwin;
7044 compose->attach_clist = attach_clist;
7046 return attach_scrwin;
7049 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7050 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7052 static GtkWidget *compose_create_others(Compose *compose)
7055 GtkWidget *savemsg_checkbtn;
7056 GtkWidget *savemsg_combo;
7057 GtkWidget *savemsg_select;
7060 gchar *folderidentifier;
7062 /* Table for settings */
7063 table = gtk_table_new(3, 1, FALSE);
7064 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7065 gtk_widget_show(table);
7066 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7069 /* Save Message to folder */
7070 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7071 gtk_widget_show(savemsg_checkbtn);
7072 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7073 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7074 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7076 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7077 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7079 #if !GTK_CHECK_VERSION(2, 24, 0)
7080 savemsg_combo = gtk_combo_box_entry_new_text();
7082 savemsg_combo = gtk_combo_box_text_new_with_entry();
7084 compose->savemsg_checkbtn = savemsg_checkbtn;
7085 compose->savemsg_combo = savemsg_combo;
7086 gtk_widget_show(savemsg_combo);
7088 if (prefs_common.compose_save_to_history)
7089 #if !GTK_CHECK_VERSION(2, 24, 0)
7090 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7091 prefs_common.compose_save_to_history);
7093 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7094 prefs_common.compose_save_to_history);
7096 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7097 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7098 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7099 G_CALLBACK(compose_grab_focus_cb), compose);
7100 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7101 folderidentifier = folder_item_get_identifier(account_get_special_folder
7102 (compose->account, F_OUTBOX));
7103 compose_set_save_to(compose, folderidentifier);
7104 g_free(folderidentifier);
7107 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7108 gtk_widget_show(savemsg_select);
7109 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7110 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7111 G_CALLBACK(compose_savemsg_select_cb),
7117 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7119 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7120 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7123 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7128 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7131 path = folder_item_get_identifier(dest);
7133 compose_set_save_to(compose, path);
7137 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7138 GdkAtom clip, GtkTextIter *insert_place);
7141 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7145 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7147 if (event->button == 3) {
7149 GtkTextIter sel_start, sel_end;
7150 gboolean stuff_selected;
7152 /* move the cursor to allow GtkAspell to check the word
7153 * under the mouse */
7154 if (event->x && event->y) {
7155 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7156 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7158 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7161 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7162 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7165 stuff_selected = gtk_text_buffer_get_selection_bounds(
7167 &sel_start, &sel_end);
7169 gtk_text_buffer_place_cursor (buffer, &iter);
7170 /* reselect stuff */
7172 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7173 gtk_text_buffer_select_range(buffer,
7174 &sel_start, &sel_end);
7176 return FALSE; /* pass the event so that the right-click goes through */
7179 if (event->button == 2) {
7184 /* get the middle-click position to paste at the correct place */
7185 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7186 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7188 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7191 entry_paste_clipboard(compose, text,
7192 prefs_common.linewrap_pastes,
7193 GDK_SELECTION_PRIMARY, &iter);
7201 static void compose_spell_menu_changed(void *data)
7203 Compose *compose = (Compose *)data;
7205 GtkWidget *menuitem;
7206 GtkWidget *parent_item;
7207 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7210 if (compose->gtkaspell == NULL)
7213 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7214 "/Menu/Spelling/Options");
7216 /* setting the submenu removes /Spelling/Options from the factory
7217 * so we need to save it */
7219 if (parent_item == NULL) {
7220 parent_item = compose->aspell_options_menu;
7221 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7223 compose->aspell_options_menu = parent_item;
7225 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7227 spell_menu = g_slist_reverse(spell_menu);
7228 for (items = spell_menu;
7229 items; items = items->next) {
7230 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7231 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7232 gtk_widget_show(GTK_WIDGET(menuitem));
7234 g_slist_free(spell_menu);
7236 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7237 gtk_widget_show(parent_item);
7240 static void compose_dict_changed(void *data)
7242 Compose *compose = (Compose *) data;
7244 if(compose->gtkaspell &&
7245 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7248 gtkaspell_highlight_all(compose->gtkaspell);
7249 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7253 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7255 Compose *compose = (Compose *)data;
7256 GdkEventButton event;
7259 event.time = gtk_get_current_event_time();
7263 return text_clicked(compose->text, &event, compose);
7266 static gboolean compose_force_window_origin = TRUE;
7267 static Compose *compose_create(PrefsAccount *account,
7276 GtkWidget *handlebox;
7278 GtkWidget *notebook;
7280 GtkWidget *attach_hbox;
7281 GtkWidget *attach_lab1;
7282 GtkWidget *attach_lab2;
7287 GtkWidget *subject_hbox;
7288 GtkWidget *subject_frame;
7289 GtkWidget *subject_entry;
7293 GtkWidget *edit_vbox;
7294 GtkWidget *ruler_hbox;
7296 GtkWidget *scrolledwin;
7298 GtkTextBuffer *buffer;
7299 GtkClipboard *clipboard;
7301 UndoMain *undostruct;
7303 GtkWidget *popupmenu;
7304 GtkWidget *tmpl_menu;
7305 GtkActionGroup *action_group = NULL;
7308 GtkAspell * gtkaspell = NULL;
7311 static GdkGeometry geometry;
7313 cm_return_val_if_fail(account != NULL, NULL);
7315 debug_print("Creating compose window...\n");
7316 compose = g_new0(Compose, 1);
7318 compose->batch = batch;
7319 compose->account = account;
7320 compose->folder = folder;
7322 compose->mutex = cm_mutex_new();
7323 compose->set_cursor_pos = -1;
7325 #if !(GTK_CHECK_VERSION(2,12,0))
7326 compose->tooltips = tips;
7329 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7331 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7332 gtk_widget_set_size_request(window, prefs_common.compose_width,
7333 prefs_common.compose_height);
7335 if (!geometry.max_width) {
7336 geometry.max_width = gdk_screen_width();
7337 geometry.max_height = gdk_screen_height();
7340 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7341 &geometry, GDK_HINT_MAX_SIZE);
7342 if (!geometry.min_width) {
7343 geometry.min_width = 600;
7344 geometry.min_height = 440;
7346 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7347 &geometry, GDK_HINT_MIN_SIZE);
7349 #ifndef GENERIC_UMPC
7350 if (compose_force_window_origin)
7351 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7352 prefs_common.compose_y);
7354 g_signal_connect(G_OBJECT(window), "delete_event",
7355 G_CALLBACK(compose_delete_cb), compose);
7356 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7357 gtk_widget_realize(window);
7359 gtkut_widget_set_composer_icon(window);
7361 vbox = gtk_vbox_new(FALSE, 0);
7362 gtk_container_add(GTK_CONTAINER(window), vbox);
7364 compose->ui_manager = gtk_ui_manager_new();
7365 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7366 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7367 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7368 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7369 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7370 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7371 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7372 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7373 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7374 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7376 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7378 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7379 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7383 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7384 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7385 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7389 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7391 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7392 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7393 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7394 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7396 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7406 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7407 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7410 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7411 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7418 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7419 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7420 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7421 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7422 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7423 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7424 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7431 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7433 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7437 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7492 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)
7493 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)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7499 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)
7500 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)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7505 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)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7509 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)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7515 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)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7521 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)
7522 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)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7535 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)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7545 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7546 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7547 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7553 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7554 gtk_widget_show_all(menubar);
7556 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7557 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7559 if (prefs_common.toolbar_detachable) {
7560 handlebox = gtk_handle_box_new();
7562 handlebox = gtk_hbox_new(FALSE, 0);
7564 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7566 gtk_widget_realize(handlebox);
7567 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7570 vbox2 = gtk_vbox_new(FALSE, 2);
7571 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7572 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7575 notebook = gtk_notebook_new();
7576 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7577 gtk_widget_show(notebook);
7579 /* header labels and entries */
7580 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7581 compose_create_header(compose),
7582 gtk_label_new_with_mnemonic(_("Hea_der")));
7583 /* attachment list */
7584 attach_hbox = gtk_hbox_new(FALSE, 0);
7585 gtk_widget_show(attach_hbox);
7587 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7588 gtk_widget_show(attach_lab1);
7589 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7591 attach_lab2 = gtk_label_new("");
7592 gtk_widget_show(attach_lab2);
7593 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7595 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7596 compose_create_attach(compose),
7599 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7600 compose_create_others(compose),
7601 gtk_label_new_with_mnemonic(_("Othe_rs")));
7604 subject_hbox = gtk_hbox_new(FALSE, 0);
7605 gtk_widget_show(subject_hbox);
7607 subject_frame = gtk_frame_new(NULL);
7608 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7609 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7610 gtk_widget_show(subject_frame);
7612 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7613 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7614 gtk_widget_show(subject);
7616 label = gtk_label_new(_("Subject:"));
7617 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7618 gtk_widget_show(label);
7621 subject_entry = claws_spell_entry_new();
7623 subject_entry = gtk_entry_new();
7625 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7626 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7627 G_CALLBACK(compose_grab_focus_cb), compose);
7628 gtk_widget_show(subject_entry);
7629 compose->subject_entry = subject_entry;
7630 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7632 edit_vbox = gtk_vbox_new(FALSE, 0);
7634 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7637 ruler_hbox = gtk_hbox_new(FALSE, 0);
7638 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7640 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7641 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7642 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7646 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7647 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7648 GTK_POLICY_AUTOMATIC,
7649 GTK_POLICY_AUTOMATIC);
7650 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7652 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7654 text = gtk_text_view_new();
7655 if (prefs_common.show_compose_margin) {
7656 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7657 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7659 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7660 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7661 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7662 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7663 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7665 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7666 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7667 G_CALLBACK(compose_notebook_size_alloc), compose);
7668 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7669 G_CALLBACK(compose_edit_size_alloc),
7671 g_signal_connect(G_OBJECT(buffer), "changed",
7672 G_CALLBACK(compose_changed_cb), compose);
7673 g_signal_connect(G_OBJECT(text), "grab_focus",
7674 G_CALLBACK(compose_grab_focus_cb), compose);
7675 g_signal_connect(G_OBJECT(buffer), "insert_text",
7676 G_CALLBACK(text_inserted), compose);
7677 g_signal_connect(G_OBJECT(text), "button_press_event",
7678 G_CALLBACK(text_clicked), compose);
7679 g_signal_connect(G_OBJECT(text), "popup-menu",
7680 G_CALLBACK(compose_popup_menu), compose);
7681 g_signal_connect(G_OBJECT(subject_entry), "changed",
7682 G_CALLBACK(compose_changed_cb), compose);
7683 g_signal_connect(G_OBJECT(subject_entry), "activate",
7684 G_CALLBACK(compose_subject_entry_activated), compose);
7687 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7688 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7689 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7690 g_signal_connect(G_OBJECT(text), "drag_data_received",
7691 G_CALLBACK(compose_insert_drag_received_cb),
7693 g_signal_connect(G_OBJECT(text), "drag-drop",
7694 G_CALLBACK(compose_drag_drop),
7696 g_signal_connect(G_OBJECT(text), "key-press-event",
7697 G_CALLBACK(completion_set_focus_to_subject),
7699 gtk_widget_show_all(vbox);
7701 /* pane between attach clist and text */
7702 paned = gtk_vpaned_new();
7703 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7704 gtk_paned_add1(GTK_PANED(paned), notebook);
7705 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7706 gtk_widget_show_all(paned);
7709 if (prefs_common.textfont) {
7710 PangoFontDescription *font_desc;
7712 font_desc = pango_font_description_from_string
7713 (prefs_common.textfont);
7715 gtk_widget_modify_font(text, font_desc);
7716 pango_font_description_free(font_desc);
7720 gtk_action_group_add_actions(action_group, compose_popup_entries,
7721 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7729 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7731 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7732 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7733 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7735 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7737 undostruct = undo_init(text);
7738 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7741 address_completion_start(window);
7743 compose->window = window;
7744 compose->vbox = vbox;
7745 compose->menubar = menubar;
7746 compose->handlebox = handlebox;
7748 compose->vbox2 = vbox2;
7750 compose->paned = paned;
7752 compose->attach_label = attach_lab2;
7754 compose->notebook = notebook;
7755 compose->edit_vbox = edit_vbox;
7756 compose->ruler_hbox = ruler_hbox;
7757 compose->ruler = ruler;
7758 compose->scrolledwin = scrolledwin;
7759 compose->text = text;
7761 compose->focused_editable = NULL;
7763 compose->popupmenu = popupmenu;
7765 compose->tmpl_menu = tmpl_menu;
7767 compose->mode = mode;
7768 compose->rmode = mode;
7770 compose->targetinfo = NULL;
7771 compose->replyinfo = NULL;
7772 compose->fwdinfo = NULL;
7774 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7775 g_str_equal, (GDestroyNotify) g_free, NULL);
7777 compose->replyto = NULL;
7779 compose->bcc = NULL;
7780 compose->followup_to = NULL;
7782 compose->ml_post = NULL;
7784 compose->inreplyto = NULL;
7785 compose->references = NULL;
7786 compose->msgid = NULL;
7787 compose->boundary = NULL;
7789 compose->autowrap = prefs_common.autowrap;
7790 compose->autoindent = prefs_common.auto_indent;
7791 compose->use_signing = FALSE;
7792 compose->use_encryption = FALSE;
7793 compose->privacy_system = NULL;
7795 compose->modified = FALSE;
7797 compose->return_receipt = FALSE;
7799 compose->to_list = NULL;
7800 compose->newsgroup_list = NULL;
7802 compose->undostruct = undostruct;
7804 compose->sig_str = NULL;
7806 compose->exteditor_file = NULL;
7807 compose->exteditor_pid = -1;
7808 compose->exteditor_tag = -1;
7809 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7812 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7813 if (mode != COMPOSE_REDIRECT) {
7814 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7815 strcmp(prefs_common.dictionary, "")) {
7816 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7817 prefs_common.alt_dictionary,
7818 conv_get_locale_charset_str(),
7819 prefs_common.misspelled_col,
7820 prefs_common.check_while_typing,
7821 prefs_common.recheck_when_changing_dict,
7822 prefs_common.use_alternate,
7823 prefs_common.use_both_dicts,
7824 GTK_TEXT_VIEW(text),
7825 GTK_WINDOW(compose->window),
7826 compose_dict_changed,
7827 compose_spell_menu_changed,
7830 alertpanel_error(_("Spell checker could not "
7832 gtkaspell_checkers_strerror());
7833 gtkaspell_checkers_reset_error();
7835 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7839 compose->gtkaspell = gtkaspell;
7840 compose_spell_menu_changed(compose);
7841 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7844 compose_select_account(compose, account, TRUE);
7846 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7847 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7849 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7850 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7852 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7853 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7855 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7856 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7858 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7859 if (account->protocol != A_NNTP)
7860 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7861 prefs_common_translated_header_name("To:"));
7863 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7864 prefs_common_translated_header_name("Newsgroups:"));
7866 #ifndef USE_NEW_ADDRBOOK
7867 addressbook_set_target_compose(compose);
7869 if (mode != COMPOSE_REDIRECT)
7870 compose_set_template_menu(compose);
7872 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7875 compose_list = g_list_append(compose_list, compose);
7877 if (!prefs_common.show_ruler)
7878 gtk_widget_hide(ruler_hbox);
7880 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7883 compose->priority = PRIORITY_NORMAL;
7884 compose_update_priority_menu_item(compose);
7886 compose_set_out_encoding(compose);
7889 compose_update_actions_menu(compose);
7891 /* Privacy Systems menu */
7892 compose_update_privacy_systems_menu(compose);
7894 activate_privacy_system(compose, account, TRUE);
7895 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7897 gtk_widget_realize(window);
7899 gtk_widget_show(window);
7905 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7910 GtkWidget *optmenubox;
7913 GtkWidget *from_name = NULL;
7914 #if !(GTK_CHECK_VERSION(2,12,0))
7915 GtkTooltips *tips = compose->tooltips;
7918 gint num = 0, def_menu = 0;
7920 accounts = account_get_list();
7921 cm_return_val_if_fail(accounts != NULL, NULL);
7923 optmenubox = gtk_event_box_new();
7924 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7925 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7927 hbox = gtk_hbox_new(FALSE, 6);
7928 from_name = gtk_entry_new();
7930 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7931 G_CALLBACK(compose_grab_focus_cb), compose);
7933 for (; accounts != NULL; accounts = accounts->next, num++) {
7934 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7935 gchar *name, *from = NULL;
7937 if (ac == compose->account) def_menu = num;
7939 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7942 if (ac == compose->account) {
7943 if (ac->name && *ac->name) {
7945 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7946 from = g_strdup_printf("%s <%s>",
7948 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7950 from = g_strdup_printf("%s",
7952 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7955 COMBOBOX_ADD(menu, name, ac->account_id);
7960 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7962 g_signal_connect(G_OBJECT(optmenu), "changed",
7963 G_CALLBACK(account_activated),
7965 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7966 G_CALLBACK(compose_entry_popup_extend),
7969 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7970 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7972 CLAWS_SET_TIP(optmenubox,
7973 _("Account to use for this email"));
7974 CLAWS_SET_TIP(from_name,
7975 _("Sender address to be used"));
7977 compose->account_combo = optmenu;
7978 compose->from_name = from_name;
7983 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7985 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7986 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7987 Compose *compose = (Compose *) data;
7989 compose->priority = value;
7993 static void compose_reply_change_mode(Compose *compose,
7996 gboolean was_modified = compose->modified;
7998 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8000 cm_return_if_fail(compose->replyinfo != NULL);
8002 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8004 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8006 if (action == COMPOSE_REPLY_TO_ALL)
8008 if (action == COMPOSE_REPLY_TO_SENDER)
8010 if (action == COMPOSE_REPLY_TO_LIST)
8013 compose_remove_header_entries(compose);
8014 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8015 if (compose->account->set_autocc && compose->account->auto_cc)
8016 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8018 if (compose->account->set_autobcc && compose->account->auto_bcc)
8019 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8021 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8022 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8023 compose_show_first_last_header(compose, TRUE);
8024 compose->modified = was_modified;
8025 compose_set_title(compose);
8028 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8030 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8031 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8032 Compose *compose = (Compose *) data;
8035 compose_reply_change_mode(compose, value);
8038 static void compose_update_priority_menu_item(Compose * compose)
8040 GtkWidget *menuitem = NULL;
8041 switch (compose->priority) {
8042 case PRIORITY_HIGHEST:
8043 menuitem = gtk_ui_manager_get_widget
8044 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8047 menuitem = gtk_ui_manager_get_widget
8048 (compose->ui_manager, "/Menu/Options/Priority/High");
8050 case PRIORITY_NORMAL:
8051 menuitem = gtk_ui_manager_get_widget
8052 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8055 menuitem = gtk_ui_manager_get_widget
8056 (compose->ui_manager, "/Menu/Options/Priority/Low");
8058 case PRIORITY_LOWEST:
8059 menuitem = gtk_ui_manager_get_widget
8060 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8063 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8066 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8068 Compose *compose = (Compose *) data;
8070 gboolean can_sign = FALSE, can_encrypt = FALSE;
8072 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8074 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8077 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8078 g_free(compose->privacy_system);
8079 compose->privacy_system = NULL;
8080 if (systemid != NULL) {
8081 compose->privacy_system = g_strdup(systemid);
8083 can_sign = privacy_system_can_sign(systemid);
8084 can_encrypt = privacy_system_can_encrypt(systemid);
8087 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8089 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8093 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8095 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8096 GtkWidget *menuitem = NULL;
8097 GList *children, *amenu;
8098 gboolean can_sign = FALSE, can_encrypt = FALSE;
8099 gboolean found = FALSE;
8101 if (compose->privacy_system != NULL) {
8103 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8104 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8105 cm_return_if_fail(menuitem != NULL);
8107 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8110 while (amenu != NULL) {
8111 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8112 if (systemid != NULL) {
8113 if (strcmp(systemid, compose->privacy_system) == 0 &&
8114 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8115 menuitem = GTK_WIDGET(amenu->data);
8117 can_sign = privacy_system_can_sign(systemid);
8118 can_encrypt = privacy_system_can_encrypt(systemid);
8122 } else if (strlen(compose->privacy_system) == 0 &&
8123 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8124 menuitem = GTK_WIDGET(amenu->data);
8127 can_encrypt = FALSE;
8132 amenu = amenu->next;
8134 g_list_free(children);
8135 if (menuitem != NULL)
8136 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8138 if (warn && !found && strlen(compose->privacy_system)) {
8139 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8140 "will not be able to sign or encrypt this message."),
8141 compose->privacy_system);
8145 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8146 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8149 static void compose_set_out_encoding(Compose *compose)
8151 CharSet out_encoding;
8152 const gchar *branch = NULL;
8153 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8155 switch(out_encoding) {
8156 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8157 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8158 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8159 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8160 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8161 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8162 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8163 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8164 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8165 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8166 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8167 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8168 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8169 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8170 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8171 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8172 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8173 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8174 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8175 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8176 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8177 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8178 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8179 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8180 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8181 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8182 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8183 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8184 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8185 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8186 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8187 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8188 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8190 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8193 static void compose_set_template_menu(Compose *compose)
8195 GSList *tmpl_list, *cur;
8199 tmpl_list = template_get_config();
8201 menu = gtk_menu_new();
8203 gtk_menu_set_accel_group (GTK_MENU (menu),
8204 gtk_ui_manager_get_accel_group(compose->ui_manager));
8205 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8206 Template *tmpl = (Template *)cur->data;
8207 gchar *accel_path = NULL;
8208 item = gtk_menu_item_new_with_label(tmpl->name);
8209 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8210 g_signal_connect(G_OBJECT(item), "activate",
8211 G_CALLBACK(compose_template_activate_cb),
8213 g_object_set_data(G_OBJECT(item), "template", tmpl);
8214 gtk_widget_show(item);
8215 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8216 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8220 gtk_widget_show(menu);
8221 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8224 void compose_update_actions_menu(Compose *compose)
8226 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8229 static void compose_update_privacy_systems_menu(Compose *compose)
8231 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8232 GSList *systems, *cur;
8234 GtkWidget *system_none;
8236 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8237 GtkWidget *privacy_menu = gtk_menu_new();
8239 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8240 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8242 g_signal_connect(G_OBJECT(system_none), "activate",
8243 G_CALLBACK(compose_set_privacy_system_cb), compose);
8245 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8246 gtk_widget_show(system_none);
8248 systems = privacy_get_system_ids();
8249 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8250 gchar *systemid = cur->data;
8252 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8253 widget = gtk_radio_menu_item_new_with_label(group,
8254 privacy_system_get_name(systemid));
8255 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8256 g_strdup(systemid), g_free);
8257 g_signal_connect(G_OBJECT(widget), "activate",
8258 G_CALLBACK(compose_set_privacy_system_cb), compose);
8260 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8261 gtk_widget_show(widget);
8264 g_slist_free(systems);
8265 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8266 gtk_widget_show_all(privacy_menu);
8267 gtk_widget_show_all(privacy_menuitem);
8270 void compose_reflect_prefs_all(void)
8275 for (cur = compose_list; cur != NULL; cur = cur->next) {
8276 compose = (Compose *)cur->data;
8277 compose_set_template_menu(compose);
8281 void compose_reflect_prefs_pixmap_theme(void)
8286 for (cur = compose_list; cur != NULL; cur = cur->next) {
8287 compose = (Compose *)cur->data;
8288 toolbar_update(TOOLBAR_COMPOSE, compose);
8292 static const gchar *compose_quote_char_from_context(Compose *compose)
8294 const gchar *qmark = NULL;
8296 cm_return_val_if_fail(compose != NULL, NULL);
8298 switch (compose->mode) {
8299 /* use forward-specific quote char */
8300 case COMPOSE_FORWARD:
8301 case COMPOSE_FORWARD_AS_ATTACH:
8302 case COMPOSE_FORWARD_INLINE:
8303 if (compose->folder && compose->folder->prefs &&
8304 compose->folder->prefs->forward_with_format)
8305 qmark = compose->folder->prefs->forward_quotemark;
8306 else if (compose->account->forward_with_format)
8307 qmark = compose->account->forward_quotemark;
8309 qmark = prefs_common.fw_quotemark;
8312 /* use reply-specific quote char in all other modes */
8314 if (compose->folder && compose->folder->prefs &&
8315 compose->folder->prefs->reply_with_format)
8316 qmark = compose->folder->prefs->reply_quotemark;
8317 else if (compose->account->reply_with_format)
8318 qmark = compose->account->reply_quotemark;
8320 qmark = prefs_common.quotemark;
8324 if (qmark == NULL || *qmark == '\0')
8330 static void compose_template_apply(Compose *compose, Template *tmpl,
8334 GtkTextBuffer *buffer;
8338 gchar *parsed_str = NULL;
8339 gint cursor_pos = 0;
8340 const gchar *err_msg = _("The body of the template has an error at line %d.");
8343 /* process the body */
8345 text = GTK_TEXT_VIEW(compose->text);
8346 buffer = gtk_text_view_get_buffer(text);
8349 qmark = compose_quote_char_from_context(compose);
8351 if (compose->replyinfo != NULL) {
8354 gtk_text_buffer_set_text(buffer, "", -1);
8355 mark = gtk_text_buffer_get_insert(buffer);
8356 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8358 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8359 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8361 } else if (compose->fwdinfo != NULL) {
8364 gtk_text_buffer_set_text(buffer, "", -1);
8365 mark = gtk_text_buffer_get_insert(buffer);
8366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8368 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8369 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8372 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8374 GtkTextIter start, end;
8377 gtk_text_buffer_get_start_iter(buffer, &start);
8378 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8379 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8381 /* clear the buffer now */
8383 gtk_text_buffer_set_text(buffer, "", -1);
8385 parsed_str = compose_quote_fmt(compose, dummyinfo,
8386 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8387 procmsg_msginfo_free( dummyinfo );
8393 gtk_text_buffer_set_text(buffer, "", -1);
8394 mark = gtk_text_buffer_get_insert(buffer);
8395 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8398 if (replace && parsed_str && compose->account->auto_sig)
8399 compose_insert_sig(compose, FALSE);
8401 if (replace && parsed_str) {
8402 gtk_text_buffer_get_start_iter(buffer, &iter);
8403 gtk_text_buffer_place_cursor(buffer, &iter);
8407 cursor_pos = quote_fmt_get_cursor_pos();
8408 compose->set_cursor_pos = cursor_pos;
8409 if (cursor_pos == -1)
8411 gtk_text_buffer_get_start_iter(buffer, &iter);
8412 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8413 gtk_text_buffer_place_cursor(buffer, &iter);
8416 /* process the other fields */
8418 compose_template_apply_fields(compose, tmpl);
8419 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8420 quote_fmt_reset_vartable();
8421 compose_changed_cb(NULL, compose);
8424 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8425 gtkaspell_highlight_all(compose->gtkaspell);
8429 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8431 MsgInfo* dummyinfo = NULL;
8432 MsgInfo *msginfo = NULL;
8435 if (compose->replyinfo != NULL)
8436 msginfo = compose->replyinfo;
8437 else if (compose->fwdinfo != NULL)
8438 msginfo = compose->fwdinfo;
8440 dummyinfo = compose_msginfo_new_from_compose(compose);
8441 msginfo = dummyinfo;
8444 if (tmpl->from && *tmpl->from != '\0') {
8446 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8447 compose->gtkaspell);
8449 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8451 quote_fmt_scan_string(tmpl->from);
8454 buf = quote_fmt_get_buffer();
8456 alertpanel_error(_("Template From format error."));
8458 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8462 if (tmpl->to && *tmpl->to != '\0') {
8464 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8465 compose->gtkaspell);
8467 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8469 quote_fmt_scan_string(tmpl->to);
8472 buf = quote_fmt_get_buffer();
8474 alertpanel_error(_("Template To format error."));
8476 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8480 if (tmpl->cc && *tmpl->cc != '\0') {
8482 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8483 compose->gtkaspell);
8485 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8487 quote_fmt_scan_string(tmpl->cc);
8490 buf = quote_fmt_get_buffer();
8492 alertpanel_error(_("Template Cc format error."));
8494 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8498 if (tmpl->bcc && *tmpl->bcc != '\0') {
8500 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8501 compose->gtkaspell);
8503 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8505 quote_fmt_scan_string(tmpl->bcc);
8508 buf = quote_fmt_get_buffer();
8510 alertpanel_error(_("Template Bcc format error."));
8512 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8516 /* process the subject */
8517 if (tmpl->subject && *tmpl->subject != '\0') {
8519 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8520 compose->gtkaspell);
8522 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8524 quote_fmt_scan_string(tmpl->subject);
8527 buf = quote_fmt_get_buffer();
8529 alertpanel_error(_("Template subject format error."));
8531 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8535 procmsg_msginfo_free( dummyinfo );
8538 static void compose_destroy(Compose *compose)
8540 GtkAllocation allocation;
8541 GtkTextBuffer *buffer;
8542 GtkClipboard *clipboard;
8544 compose_list = g_list_remove(compose_list, compose);
8546 if (compose->updating) {
8547 debug_print("danger, not destroying anything now\n");
8548 compose->deferred_destroy = TRUE;
8551 /* NOTE: address_completion_end() does nothing with the window
8552 * however this may change. */
8553 address_completion_end(compose->window);
8555 slist_free_strings_full(compose->to_list);
8556 slist_free_strings_full(compose->newsgroup_list);
8557 slist_free_strings_full(compose->header_list);
8559 slist_free_strings_full(extra_headers);
8560 extra_headers = NULL;
8562 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8564 g_hash_table_destroy(compose->email_hashtable);
8566 procmsg_msginfo_free(compose->targetinfo);
8567 procmsg_msginfo_free(compose->replyinfo);
8568 procmsg_msginfo_free(compose->fwdinfo);
8570 g_free(compose->replyto);
8571 g_free(compose->cc);
8572 g_free(compose->bcc);
8573 g_free(compose->newsgroups);
8574 g_free(compose->followup_to);
8576 g_free(compose->ml_post);
8578 g_free(compose->inreplyto);
8579 g_free(compose->references);
8580 g_free(compose->msgid);
8581 g_free(compose->boundary);
8583 g_free(compose->redirect_filename);
8584 if (compose->undostruct)
8585 undo_destroy(compose->undostruct);
8587 g_free(compose->sig_str);
8589 g_free(compose->exteditor_file);
8591 g_free(compose->orig_charset);
8593 g_free(compose->privacy_system);
8595 #ifndef USE_NEW_ADDRBOOK
8596 if (addressbook_get_target_compose() == compose)
8597 addressbook_set_target_compose(NULL);
8600 if (compose->gtkaspell) {
8601 gtkaspell_delete(compose->gtkaspell);
8602 compose->gtkaspell = NULL;
8606 if (!compose->batch) {
8607 gtk_widget_get_allocation(compose->window, &allocation);
8608 prefs_common.compose_width = allocation.width;
8609 prefs_common.compose_height = allocation.height;
8612 if (!gtk_widget_get_parent(compose->paned))
8613 gtk_widget_destroy(compose->paned);
8614 gtk_widget_destroy(compose->popupmenu);
8616 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8617 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8618 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8620 gtk_widget_destroy(compose->window);
8621 toolbar_destroy(compose->toolbar);
8622 g_free(compose->toolbar);
8623 cm_mutex_free(compose->mutex);
8627 static void compose_attach_info_free(AttachInfo *ainfo)
8629 g_free(ainfo->file);
8630 g_free(ainfo->content_type);
8631 g_free(ainfo->name);
8632 g_free(ainfo->charset);
8636 static void compose_attach_update_label(Compose *compose)
8641 GtkTreeModel *model;
8646 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8647 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8648 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8652 while(gtk_tree_model_iter_next(model, &iter))
8655 text = g_strdup_printf("(%d)", i);
8656 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8660 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8662 Compose *compose = (Compose *)data;
8663 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8664 GtkTreeSelection *selection;
8666 GtkTreeModel *model;
8668 selection = gtk_tree_view_get_selection(tree_view);
8669 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8674 for (cur = sel; cur != NULL; cur = cur->next) {
8675 GtkTreePath *path = cur->data;
8676 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8679 gtk_tree_path_free(path);
8682 for (cur = sel; cur != NULL; cur = cur->next) {
8683 GtkTreeRowReference *ref = cur->data;
8684 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8687 if (gtk_tree_model_get_iter(model, &iter, path))
8688 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8690 gtk_tree_path_free(path);
8691 gtk_tree_row_reference_free(ref);
8695 compose_attach_update_label(compose);
8698 static struct _AttachProperty
8701 GtkWidget *mimetype_entry;
8702 GtkWidget *encoding_optmenu;
8703 GtkWidget *path_entry;
8704 GtkWidget *filename_entry;
8706 GtkWidget *cancel_btn;
8709 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8711 gtk_tree_path_free((GtkTreePath *)ptr);
8714 static void compose_attach_property(GtkAction *action, gpointer data)
8716 Compose *compose = (Compose *)data;
8717 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8719 GtkComboBox *optmenu;
8720 GtkTreeSelection *selection;
8722 GtkTreeModel *model;
8725 static gboolean cancelled;
8727 /* only if one selected */
8728 selection = gtk_tree_view_get_selection(tree_view);
8729 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8732 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8736 path = (GtkTreePath *) sel->data;
8737 gtk_tree_model_get_iter(model, &iter, path);
8738 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8741 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8747 if (!attach_prop.window)
8748 compose_attach_property_create(&cancelled);
8749 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8750 gtk_widget_grab_focus(attach_prop.ok_btn);
8751 gtk_widget_show(attach_prop.window);
8752 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8753 GTK_WINDOW(compose->window));
8755 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8756 if (ainfo->encoding == ENC_UNKNOWN)
8757 combobox_select_by_data(optmenu, ENC_BASE64);
8759 combobox_select_by_data(optmenu, ainfo->encoding);
8761 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8762 ainfo->content_type ? ainfo->content_type : "");
8763 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8764 ainfo->file ? ainfo->file : "");
8765 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8766 ainfo->name ? ainfo->name : "");
8769 const gchar *entry_text;
8771 gchar *cnttype = NULL;
8778 gtk_widget_hide(attach_prop.window);
8779 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8784 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8785 if (*entry_text != '\0') {
8788 text = g_strstrip(g_strdup(entry_text));
8789 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8790 cnttype = g_strdup(text);
8793 alertpanel_error(_("Invalid MIME type."));
8799 ainfo->encoding = combobox_get_active_data(optmenu);
8801 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8802 if (*entry_text != '\0') {
8803 if (is_file_exist(entry_text) &&
8804 (size = get_file_size(entry_text)) > 0)
8805 file = g_strdup(entry_text);
8808 (_("File doesn't exist or is empty."));
8814 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8815 if (*entry_text != '\0') {
8816 g_free(ainfo->name);
8817 ainfo->name = g_strdup(entry_text);
8821 g_free(ainfo->content_type);
8822 ainfo->content_type = cnttype;
8825 g_free(ainfo->file);
8829 ainfo->size = (goffset)size;
8831 /* update tree store */
8832 text = to_human_readable(ainfo->size);
8833 gtk_tree_model_get_iter(model, &iter, path);
8834 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8835 COL_MIMETYPE, ainfo->content_type,
8837 COL_NAME, ainfo->name,
8838 COL_CHARSET, ainfo->charset,
8844 gtk_tree_path_free(path);
8847 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8849 label = gtk_label_new(str); \
8850 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8851 GTK_FILL, 0, 0, 0); \
8852 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8854 entry = gtk_entry_new(); \
8855 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8856 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8859 static void compose_attach_property_create(gboolean *cancelled)
8865 GtkWidget *mimetype_entry;
8868 GtkListStore *optmenu_menu;
8869 GtkWidget *path_entry;
8870 GtkWidget *filename_entry;
8873 GtkWidget *cancel_btn;
8874 GList *mime_type_list, *strlist;
8877 debug_print("Creating attach_property window...\n");
8879 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8880 gtk_widget_set_size_request(window, 480, -1);
8881 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8882 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8883 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8884 g_signal_connect(G_OBJECT(window), "delete_event",
8885 G_CALLBACK(attach_property_delete_event),
8887 g_signal_connect(G_OBJECT(window), "key_press_event",
8888 G_CALLBACK(attach_property_key_pressed),
8891 vbox = gtk_vbox_new(FALSE, 8);
8892 gtk_container_add(GTK_CONTAINER(window), vbox);
8894 table = gtk_table_new(4, 2, FALSE);
8895 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8896 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8897 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8899 label = gtk_label_new(_("MIME type"));
8900 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8902 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8903 #if !GTK_CHECK_VERSION(2, 24, 0)
8904 mimetype_entry = gtk_combo_box_entry_new_text();
8906 mimetype_entry = gtk_combo_box_text_new_with_entry();
8908 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8909 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8911 /* stuff with list */
8912 mime_type_list = procmime_get_mime_type_list();
8914 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8915 MimeType *type = (MimeType *) mime_type_list->data;
8918 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8920 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8923 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8924 (GCompareFunc)strcmp2);
8927 for (mime_type_list = strlist; mime_type_list != NULL;
8928 mime_type_list = mime_type_list->next) {
8929 #if !GTK_CHECK_VERSION(2, 24, 0)
8930 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8932 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8934 g_free(mime_type_list->data);
8936 g_list_free(strlist);
8937 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8938 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8940 label = gtk_label_new(_("Encoding"));
8941 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8943 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8945 hbox = gtk_hbox_new(FALSE, 0);
8946 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8947 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8949 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8950 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8952 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8953 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8954 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8955 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8956 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8958 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8960 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8961 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8963 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8964 &ok_btn, GTK_STOCK_OK,
8966 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8967 gtk_widget_grab_default(ok_btn);
8969 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8970 G_CALLBACK(attach_property_ok),
8972 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8973 G_CALLBACK(attach_property_cancel),
8976 gtk_widget_show_all(vbox);
8978 attach_prop.window = window;
8979 attach_prop.mimetype_entry = mimetype_entry;
8980 attach_prop.encoding_optmenu = optmenu;
8981 attach_prop.path_entry = path_entry;
8982 attach_prop.filename_entry = filename_entry;
8983 attach_prop.ok_btn = ok_btn;
8984 attach_prop.cancel_btn = cancel_btn;
8987 #undef SET_LABEL_AND_ENTRY
8989 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8995 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9001 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9002 gboolean *cancelled)
9010 static gboolean attach_property_key_pressed(GtkWidget *widget,
9012 gboolean *cancelled)
9014 if (event && event->keyval == GDK_KEY_Escape) {
9018 if (event && event->keyval == GDK_KEY_Return) {
9026 static void compose_exec_ext_editor(Compose *compose)
9033 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9034 G_DIR_SEPARATOR, compose);
9036 if (pipe(pipe_fds) < 0) {
9042 if ((pid = fork()) < 0) {
9049 /* close the write side of the pipe */
9052 compose->exteditor_file = g_strdup(tmp);
9053 compose->exteditor_pid = pid;
9055 compose_set_ext_editor_sensitive(compose, FALSE);
9058 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9060 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9062 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9066 } else { /* process-monitoring process */
9072 /* close the read side of the pipe */
9075 if (compose_write_body_to_file(compose, tmp) < 0) {
9076 fd_write_all(pipe_fds[1], "2\n", 2);
9080 pid_ed = compose_exec_ext_editor_real(tmp);
9082 fd_write_all(pipe_fds[1], "1\n", 2);
9086 /* wait until editor is terminated */
9087 waitpid(pid_ed, NULL, 0);
9089 fd_write_all(pipe_fds[1], "0\n", 2);
9096 #endif /* G_OS_UNIX */
9100 static gint compose_exec_ext_editor_real(const gchar *file)
9107 cm_return_val_if_fail(file != NULL, -1);
9109 if ((pid = fork()) < 0) {
9114 if (pid != 0) return pid;
9116 /* grandchild process */
9118 if (setpgid(0, getppid()))
9121 if (prefs_common_get_ext_editor_cmd() &&
9122 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9123 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9124 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9126 if (prefs_common_get_ext_editor_cmd())
9127 g_warning("External editor command-line is invalid: '%s'\n",
9128 prefs_common_get_ext_editor_cmd());
9129 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9132 cmdline = strsplit_with_quote(buf, " ", 1024);
9133 execvp(cmdline[0], cmdline);
9136 g_strfreev(cmdline);
9141 static gboolean compose_ext_editor_kill(Compose *compose)
9143 pid_t pgid = compose->exteditor_pid * -1;
9146 ret = kill(pgid, 0);
9148 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9152 msg = g_strdup_printf
9153 (_("The external editor is still working.\n"
9154 "Force terminating the process?\n"
9155 "process group id: %d"), -pgid);
9156 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9157 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9161 if (val == G_ALERTALTERNATE) {
9162 g_source_remove(compose->exteditor_tag);
9163 g_io_channel_shutdown(compose->exteditor_ch,
9165 g_io_channel_unref(compose->exteditor_ch);
9167 if (kill(pgid, SIGTERM) < 0) perror("kill");
9168 waitpid(compose->exteditor_pid, NULL, 0);
9170 g_warning("Terminated process group id: %d", -pgid);
9171 g_warning("Temporary file: %s",
9172 compose->exteditor_file);
9174 compose_set_ext_editor_sensitive(compose, TRUE);
9176 g_free(compose->exteditor_file);
9177 compose->exteditor_file = NULL;
9178 compose->exteditor_pid = -1;
9179 compose->exteditor_ch = NULL;
9180 compose->exteditor_tag = -1;
9188 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9192 Compose *compose = (Compose *)data;
9195 debug_print("Compose: input from monitoring process\n");
9197 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9199 g_io_channel_shutdown(source, FALSE, NULL);
9200 g_io_channel_unref(source);
9202 waitpid(compose->exteditor_pid, NULL, 0);
9204 if (buf[0] == '0') { /* success */
9205 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9206 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9208 gtk_text_buffer_set_text(buffer, "", -1);
9209 compose_insert_file(compose, compose->exteditor_file);
9210 compose_changed_cb(NULL, compose);
9211 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9213 if (claws_unlink(compose->exteditor_file) < 0)
9214 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9215 } else if (buf[0] == '1') { /* failed */
9216 g_warning("Couldn't exec external editor\n");
9217 if (claws_unlink(compose->exteditor_file) < 0)
9218 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9219 } else if (buf[0] == '2') {
9220 g_warning("Couldn't write to file\n");
9221 } else if (buf[0] == '3') {
9222 g_warning("Pipe read failed\n");
9225 compose_set_ext_editor_sensitive(compose, TRUE);
9227 g_free(compose->exteditor_file);
9228 compose->exteditor_file = NULL;
9229 compose->exteditor_pid = -1;
9230 compose->exteditor_ch = NULL;
9231 compose->exteditor_tag = -1;
9236 static void compose_set_ext_editor_sensitive(Compose *compose,
9239 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9241 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9242 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9247 gtk_widget_set_sensitive(compose->text, sensitive);
9248 if (compose->toolbar->send_btn)
9249 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9250 if (compose->toolbar->sendl_btn)
9251 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9252 if (compose->toolbar->draft_btn)
9253 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9254 if (compose->toolbar->insert_btn)
9255 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9256 if (compose->toolbar->sig_btn)
9257 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9258 if (compose->toolbar->exteditor_btn)
9259 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9260 if (compose->toolbar->linewrap_current_btn)
9261 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9262 if (compose->toolbar->linewrap_all_btn)
9263 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9265 #endif /* G_OS_UNIX */
9268 * compose_undo_state_changed:
9270 * Change the sensivity of the menuentries undo and redo
9272 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9273 gint redo_state, gpointer data)
9275 Compose *compose = (Compose *)data;
9277 switch (undo_state) {
9278 case UNDO_STATE_TRUE:
9279 if (!undostruct->undo_state) {
9280 undostruct->undo_state = TRUE;
9281 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9284 case UNDO_STATE_FALSE:
9285 if (undostruct->undo_state) {
9286 undostruct->undo_state = FALSE;
9287 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9290 case UNDO_STATE_UNCHANGED:
9292 case UNDO_STATE_REFRESH:
9293 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9296 g_warning("Undo state not recognized");
9300 switch (redo_state) {
9301 case UNDO_STATE_TRUE:
9302 if (!undostruct->redo_state) {
9303 undostruct->redo_state = TRUE;
9304 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9307 case UNDO_STATE_FALSE:
9308 if (undostruct->redo_state) {
9309 undostruct->redo_state = FALSE;
9310 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9313 case UNDO_STATE_UNCHANGED:
9315 case UNDO_STATE_REFRESH:
9316 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9319 g_warning("Redo state not recognized");
9324 /* callback functions */
9326 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9327 GtkAllocation *allocation,
9330 prefs_common.compose_notebook_height = allocation->height;
9333 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9334 * includes "non-client" (windows-izm) in calculation, so this calculation
9335 * may not be accurate.
9337 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9338 GtkAllocation *allocation,
9339 GtkSHRuler *shruler)
9341 if (prefs_common.show_ruler) {
9342 gint char_width = 0, char_height = 0;
9343 gint line_width_in_chars;
9345 gtkut_get_font_size(GTK_WIDGET(widget),
9346 &char_width, &char_height);
9347 line_width_in_chars =
9348 (allocation->width - allocation->x) / char_width;
9350 /* got the maximum */
9351 gtk_shruler_set_range(GTK_SHRULER(shruler),
9352 0.0, line_width_in_chars, 0);
9361 ComposePrefType type;
9362 gboolean entry_marked;
9365 static void account_activated(GtkComboBox *optmenu, gpointer data)
9367 Compose *compose = (Compose *)data;
9370 gchar *folderidentifier;
9371 gint account_id = 0;
9374 GSList *list, *saved_list = NULL;
9375 HeaderEntryState *state;
9376 GtkRcStyle *style = NULL;
9377 #if !GTK_CHECK_VERSION(3, 0, 0)
9378 static GdkColor yellow;
9379 static gboolean color_set = FALSE;
9381 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9384 /* Get ID of active account in the combo box */
9385 menu = gtk_combo_box_get_model(optmenu);
9386 gtk_combo_box_get_active_iter(optmenu, &iter);
9387 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9389 ac = account_find_from_id(account_id);
9390 cm_return_if_fail(ac != NULL);
9392 if (ac != compose->account) {
9393 compose_select_account(compose, ac, FALSE);
9395 for (list = compose->header_list; list; list = list->next) {
9396 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9398 if (hentry->type == PREF_ACCOUNT || !list->next) {
9399 compose_destroy_headerentry(compose, hentry);
9403 state = g_malloc0(sizeof(HeaderEntryState));
9404 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9405 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9406 state->entry = gtk_editable_get_chars(
9407 GTK_EDITABLE(hentry->entry), 0, -1);
9408 state->type = hentry->type;
9410 #if !GTK_CHECK_VERSION(3, 0, 0)
9412 gdk_color_parse("#f5f6be", &yellow);
9413 color_set = gdk_colormap_alloc_color(
9414 gdk_colormap_get_system(),
9415 &yellow, FALSE, TRUE);
9419 style = gtk_widget_get_modifier_style(hentry->entry);
9420 state->entry_marked = gdk_color_equal(&yellow,
9421 &style->base[GTK_STATE_NORMAL]);
9423 saved_list = g_slist_append(saved_list, state);
9424 compose_destroy_headerentry(compose, hentry);
9427 compose->header_last = NULL;
9428 g_slist_free(compose->header_list);
9429 compose->header_list = NULL;
9430 compose->header_nextrow = 1;
9431 compose_create_header_entry(compose);
9433 if (ac->set_autocc && ac->auto_cc)
9434 compose_entry_append(compose, ac->auto_cc,
9435 COMPOSE_CC, PREF_ACCOUNT);
9437 if (ac->set_autobcc && ac->auto_bcc)
9438 compose_entry_append(compose, ac->auto_bcc,
9439 COMPOSE_BCC, PREF_ACCOUNT);
9441 if (ac->set_autoreplyto && ac->auto_replyto)
9442 compose_entry_append(compose, ac->auto_replyto,
9443 COMPOSE_REPLYTO, PREF_ACCOUNT);
9445 for (list = saved_list; list; list = list->next) {
9446 state = (HeaderEntryState *) list->data;
9448 compose_add_header_entry(compose, state->header,
9449 state->entry, state->type);
9450 if (state->entry_marked)
9451 compose_entry_mark_default_to(compose, state->entry);
9453 g_free(state->header);
9454 g_free(state->entry);
9457 g_slist_free(saved_list);
9459 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9460 (ac->protocol == A_NNTP) ?
9461 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9464 /* Set message save folder */
9465 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9466 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9468 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9469 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9471 compose_set_save_to(compose, NULL);
9472 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9473 folderidentifier = folder_item_get_identifier(account_get_special_folder
9474 (compose->account, F_OUTBOX));
9475 compose_set_save_to(compose, folderidentifier);
9476 g_free(folderidentifier);
9480 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9481 GtkTreeViewColumn *column, Compose *compose)
9483 compose_attach_property(NULL, compose);
9486 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9489 Compose *compose = (Compose *)data;
9490 GtkTreeSelection *attach_selection;
9491 gint attach_nr_selected;
9493 if (!event) return FALSE;
9495 if (event->button == 3) {
9496 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9497 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9499 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9500 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9502 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9503 NULL, NULL, event->button, event->time);
9510 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9513 Compose *compose = (Compose *)data;
9515 if (!event) return FALSE;
9517 switch (event->keyval) {
9518 case GDK_KEY_Delete:
9519 compose_attach_remove_selected(NULL, compose);
9525 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9527 toolbar_comp_set_sensitive(compose, allow);
9528 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9529 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9531 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9533 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9534 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9537 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9541 static void compose_send_cb(GtkAction *action, gpointer data)
9543 Compose *compose = (Compose *)data;
9545 if (prefs_common.work_offline &&
9546 !inc_offline_should_override(TRUE,
9547 _("Claws Mail needs network access in order "
9548 "to send this email.")))
9551 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9552 g_source_remove(compose->draft_timeout_tag);
9553 compose->draft_timeout_tag = -1;
9556 compose_send(compose);
9559 static void compose_send_later_cb(GtkAction *action, gpointer data)
9561 Compose *compose = (Compose *)data;
9565 compose_allow_user_actions(compose, FALSE);
9566 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9567 compose_allow_user_actions(compose, TRUE);
9571 compose_close(compose);
9572 } else if (val == -1) {
9573 alertpanel_error(_("Could not queue message."));
9574 } else if (val == -2) {
9575 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9576 } else if (val == -3) {
9577 if (privacy_peek_error())
9578 alertpanel_error(_("Could not queue message for sending:\n\n"
9579 "Signature failed: %s"), privacy_get_error());
9580 } else if (val == -4) {
9581 alertpanel_error(_("Could not queue message for sending:\n\n"
9582 "Charset conversion failed."));
9583 } else if (val == -5) {
9584 alertpanel_error(_("Could not queue message for sending:\n\n"
9585 "Couldn't get recipient encryption key."));
9586 } else if (val == -6) {
9589 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9592 #define DRAFTED_AT_EXIT "drafted_at_exit"
9593 static void compose_register_draft(MsgInfo *info)
9595 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9596 DRAFTED_AT_EXIT, NULL);
9597 FILE *fp = g_fopen(filepath, "ab");
9600 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9608 gboolean compose_draft (gpointer data, guint action)
9610 Compose *compose = (Compose *)data;
9615 MsgFlags flag = {0, 0};
9616 static gboolean lock = FALSE;
9617 MsgInfo *newmsginfo;
9619 gboolean target_locked = FALSE;
9620 gboolean err = FALSE;
9622 if (lock) return FALSE;
9624 if (compose->sending)
9627 draft = account_get_special_folder(compose->account, F_DRAFT);
9628 cm_return_val_if_fail(draft != NULL, FALSE);
9630 if (!g_mutex_trylock(compose->mutex)) {
9631 /* we don't want to lock the mutex once it's available,
9632 * because as the only other part of compose.c locking
9633 * it is compose_close - which means once unlocked,
9634 * the compose struct will be freed */
9635 debug_print("couldn't lock mutex, probably sending\n");
9641 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9642 G_DIR_SEPARATOR, compose);
9643 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9644 FILE_OP_ERROR(tmp, "fopen");
9648 /* chmod for security */
9649 if (change_file_mode_rw(fp, tmp) < 0) {
9650 FILE_OP_ERROR(tmp, "chmod");
9651 g_warning("can't change file mode\n");
9654 /* Save draft infos */
9655 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9656 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9658 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9659 gchar *savefolderid;
9661 savefolderid = compose_get_save_to(compose);
9662 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9663 g_free(savefolderid);
9665 if (compose->return_receipt) {
9666 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9668 if (compose->privacy_system) {
9669 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9670 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9671 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9674 /* Message-ID of message replying to */
9675 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9678 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9679 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9682 /* Message-ID of message forwarding to */
9683 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9686 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9687 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9691 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9692 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9694 sheaders = compose_get_manual_headers_info(compose);
9695 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9698 /* end of headers */
9699 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9706 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9710 if (fclose(fp) == EOF) {
9714 if (compose->targetinfo) {
9715 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9716 flag.perm_flags = target_locked?MSG_LOCKED:0;
9718 flag.tmp_flags = MSG_DRAFT;
9720 folder_item_scan(draft);
9721 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9722 MsgInfo *tmpinfo = NULL;
9723 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9724 if (compose->msgid) {
9725 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9728 msgnum = tmpinfo->msgnum;
9729 procmsg_msginfo_free(tmpinfo);
9730 debug_print("got draft msgnum %d from scanning\n", msgnum);
9732 debug_print("didn't get draft msgnum after scanning\n");
9735 debug_print("got draft msgnum %d from adding\n", msgnum);
9741 if (action != COMPOSE_AUTO_SAVE) {
9742 if (action != COMPOSE_DRAFT_FOR_EXIT)
9743 alertpanel_error(_("Could not save draft."));
9746 gtkut_window_popup(compose->window);
9747 val = alertpanel_full(_("Could not save draft"),
9748 _("Could not save draft.\n"
9749 "Do you want to cancel exit or discard this email?"),
9750 _("_Cancel exit"), _("_Discard email"), NULL,
9751 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9752 if (val == G_ALERTALTERNATE) {
9754 g_mutex_unlock(compose->mutex); /* must be done before closing */
9755 compose_close(compose);
9759 g_mutex_unlock(compose->mutex); /* must be done before closing */
9768 if (compose->mode == COMPOSE_REEDIT) {
9769 compose_remove_reedit_target(compose, TRUE);
9772 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9775 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9777 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9779 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9780 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9781 procmsg_msginfo_set_flags(newmsginfo, 0,
9782 MSG_HAS_ATTACHMENT);
9784 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9785 compose_register_draft(newmsginfo);
9787 procmsg_msginfo_free(newmsginfo);
9790 folder_item_scan(draft);
9792 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9794 g_mutex_unlock(compose->mutex); /* must be done before closing */
9795 compose_close(compose);
9801 path = folder_item_fetch_msg(draft, msgnum);
9803 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9806 if (g_stat(path, &s) < 0) {
9807 FILE_OP_ERROR(path, "stat");
9813 procmsg_msginfo_free(compose->targetinfo);
9814 compose->targetinfo = procmsg_msginfo_new();
9815 compose->targetinfo->msgnum = msgnum;
9816 compose->targetinfo->size = (goffset)s.st_size;
9817 compose->targetinfo->mtime = s.st_mtime;
9818 compose->targetinfo->folder = draft;
9820 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9821 compose->mode = COMPOSE_REEDIT;
9823 if (action == COMPOSE_AUTO_SAVE) {
9824 compose->autosaved_draft = compose->targetinfo;
9826 compose->modified = FALSE;
9827 compose_set_title(compose);
9831 g_mutex_unlock(compose->mutex);
9835 void compose_clear_exit_drafts(void)
9837 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9838 DRAFTED_AT_EXIT, NULL);
9839 if (is_file_exist(filepath))
9840 claws_unlink(filepath);
9845 void compose_reopen_exit_drafts(void)
9847 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9848 DRAFTED_AT_EXIT, NULL);
9849 FILE *fp = g_fopen(filepath, "rb");
9853 while (fgets(buf, sizeof(buf), fp)) {
9854 gchar **parts = g_strsplit(buf, "\t", 2);
9855 const gchar *folder = parts[0];
9856 int msgnum = parts[1] ? atoi(parts[1]):-1;
9858 if (folder && *folder && msgnum > -1) {
9859 FolderItem *item = folder_find_item_from_identifier(folder);
9860 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9862 compose_reedit(info, FALSE);
9869 compose_clear_exit_drafts();
9872 static void compose_save_cb(GtkAction *action, gpointer data)
9874 Compose *compose = (Compose *)data;
9875 compose_draft(compose, COMPOSE_KEEP_EDITING);
9876 compose->rmode = COMPOSE_REEDIT;
9879 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9881 if (compose && file_list) {
9884 for ( tmp = file_list; tmp; tmp = tmp->next) {
9885 gchar *file = (gchar *) tmp->data;
9886 gchar *utf8_filename = conv_filename_to_utf8(file);
9887 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9888 compose_changed_cb(NULL, compose);
9893 g_free(utf8_filename);
9898 static void compose_attach_cb(GtkAction *action, gpointer data)
9900 Compose *compose = (Compose *)data;
9903 if (compose->redirect_filename != NULL)
9906 /* Set focus_window properly, in case we were called via popup menu,
9907 * which unsets it (via focus_out_event callback on compose window). */
9908 manage_window_focus_in(compose->window, NULL, NULL);
9910 file_list = filesel_select_multiple_files_open(_("Select file"));
9913 compose_attach_from_list(compose, file_list, TRUE);
9914 g_list_free(file_list);
9918 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9920 Compose *compose = (Compose *)data;
9922 gint files_inserted = 0;
9924 file_list = filesel_select_multiple_files_open(_("Select file"));
9929 for ( tmp = file_list; tmp; tmp = tmp->next) {
9930 gchar *file = (gchar *) tmp->data;
9931 gchar *filedup = g_strdup(file);
9932 gchar *shortfile = g_path_get_basename(filedup);
9933 ComposeInsertResult res;
9934 /* insert the file if the file is short or if the user confirmed that
9935 he/she wants to insert the large file */
9936 res = compose_insert_file(compose, file);
9937 if (res == COMPOSE_INSERT_READ_ERROR) {
9938 alertpanel_error(_("File '%s' could not be read."), shortfile);
9939 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9940 alertpanel_error(_("File '%s' contained invalid characters\n"
9941 "for the current encoding, insertion may be incorrect."),
9943 } else if (res == COMPOSE_INSERT_SUCCESS)
9950 g_list_free(file_list);
9954 if (files_inserted > 0 && compose->gtkaspell &&
9955 compose->gtkaspell->check_while_typing)
9956 gtkaspell_highlight_all(compose->gtkaspell);
9960 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9962 Compose *compose = (Compose *)data;
9964 compose_insert_sig(compose, FALSE);
9967 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9971 Compose *compose = (Compose *)data;
9973 gtkut_widget_get_uposition(widget, &x, &y);
9974 if (!compose->batch) {
9975 prefs_common.compose_x = x;
9976 prefs_common.compose_y = y;
9978 if (compose->sending || compose->updating)
9980 compose_close_cb(NULL, compose);
9984 void compose_close_toolbar(Compose *compose)
9986 compose_close_cb(NULL, compose);
9989 static void compose_close_cb(GtkAction *action, gpointer data)
9991 Compose *compose = (Compose *)data;
9995 if (compose->exteditor_tag != -1) {
9996 if (!compose_ext_editor_kill(compose))
10001 if (compose->modified) {
10002 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10003 if (!g_mutex_trylock(compose->mutex)) {
10004 /* we don't want to lock the mutex once it's available,
10005 * because as the only other part of compose.c locking
10006 * it is compose_close - which means once unlocked,
10007 * the compose struct will be freed */
10008 debug_print("couldn't lock mutex, probably sending\n");
10012 val = alertpanel(_("Discard message"),
10013 _("This message has been modified. Discard it?"),
10014 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10016 val = alertpanel(_("Save changes"),
10017 _("This message has been modified. Save the latest changes?"),
10018 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10020 g_mutex_unlock(compose->mutex);
10022 case G_ALERTDEFAULT:
10023 if (prefs_common.autosave && !reedit)
10024 compose_remove_draft(compose);
10026 case G_ALERTALTERNATE:
10027 compose_draft(data, COMPOSE_QUIT_EDITING);
10034 compose_close(compose);
10037 static void compose_print_cb(GtkAction *action, gpointer data)
10039 Compose *compose = (Compose *) data;
10041 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10042 if (compose->targetinfo)
10043 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10046 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10048 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10049 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10050 Compose *compose = (Compose *) data;
10053 compose->out_encoding = (CharSet)value;
10056 static void compose_address_cb(GtkAction *action, gpointer data)
10058 Compose *compose = (Compose *)data;
10060 #ifndef USE_NEW_ADDRBOOK
10061 addressbook_open(compose);
10063 GError* error = NULL;
10064 addressbook_connect_signals(compose);
10065 addressbook_dbus_open(TRUE, &error);
10067 g_warning("%s", error->message);
10068 g_error_free(error);
10073 static void about_show_cb(GtkAction *action, gpointer data)
10078 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10080 Compose *compose = (Compose *)data;
10085 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10086 cm_return_if_fail(tmpl != NULL);
10088 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10090 val = alertpanel(_("Apply template"), msg,
10091 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10094 if (val == G_ALERTDEFAULT)
10095 compose_template_apply(compose, tmpl, TRUE);
10096 else if (val == G_ALERTALTERNATE)
10097 compose_template_apply(compose, tmpl, FALSE);
10100 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10102 Compose *compose = (Compose *)data;
10104 compose_exec_ext_editor(compose);
10107 static void compose_undo_cb(GtkAction *action, gpointer data)
10109 Compose *compose = (Compose *)data;
10110 gboolean prev_autowrap = compose->autowrap;
10112 compose->autowrap = FALSE;
10113 undo_undo(compose->undostruct);
10114 compose->autowrap = prev_autowrap;
10117 static void compose_redo_cb(GtkAction *action, gpointer data)
10119 Compose *compose = (Compose *)data;
10120 gboolean prev_autowrap = compose->autowrap;
10122 compose->autowrap = FALSE;
10123 undo_redo(compose->undostruct);
10124 compose->autowrap = prev_autowrap;
10127 static void entry_cut_clipboard(GtkWidget *entry)
10129 if (GTK_IS_EDITABLE(entry))
10130 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10131 else if (GTK_IS_TEXT_VIEW(entry))
10132 gtk_text_buffer_cut_clipboard(
10133 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10134 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10138 static void entry_copy_clipboard(GtkWidget *entry)
10140 if (GTK_IS_EDITABLE(entry))
10141 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10142 else if (GTK_IS_TEXT_VIEW(entry))
10143 gtk_text_buffer_copy_clipboard(
10144 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10145 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10148 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10149 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10151 if (GTK_IS_TEXT_VIEW(entry)) {
10152 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10153 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10154 GtkTextIter start_iter, end_iter;
10156 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10158 if (contents == NULL)
10161 /* we shouldn't delete the selection when middle-click-pasting, or we
10162 * can't mid-click-paste our own selection */
10163 if (clip != GDK_SELECTION_PRIMARY) {
10164 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10165 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10168 if (insert_place == NULL) {
10169 /* if insert_place isn't specified, insert at the cursor.
10170 * used for Ctrl-V pasting */
10171 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10172 start = gtk_text_iter_get_offset(&start_iter);
10173 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10175 /* if insert_place is specified, paste here.
10176 * used for mid-click-pasting */
10177 start = gtk_text_iter_get_offset(insert_place);
10178 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10179 if (prefs_common.primary_paste_unselects)
10180 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10184 /* paste unwrapped: mark the paste so it's not wrapped later */
10185 end = start + strlen(contents);
10186 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10187 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10188 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10189 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10190 /* rewrap paragraph now (after a mid-click-paste) */
10191 mark_start = gtk_text_buffer_get_insert(buffer);
10192 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10193 gtk_text_iter_backward_char(&start_iter);
10194 compose_beautify_paragraph(compose, &start_iter, TRUE);
10196 } else if (GTK_IS_EDITABLE(entry))
10197 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10199 compose->modified = TRUE;
10202 static void entry_allsel(GtkWidget *entry)
10204 if (GTK_IS_EDITABLE(entry))
10205 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10206 else if (GTK_IS_TEXT_VIEW(entry)) {
10207 GtkTextIter startiter, enditer;
10208 GtkTextBuffer *textbuf;
10210 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10211 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10212 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10214 gtk_text_buffer_move_mark_by_name(textbuf,
10215 "selection_bound", &startiter);
10216 gtk_text_buffer_move_mark_by_name(textbuf,
10217 "insert", &enditer);
10221 static void compose_cut_cb(GtkAction *action, gpointer data)
10223 Compose *compose = (Compose *)data;
10224 if (compose->focused_editable
10225 #ifndef GENERIC_UMPC
10226 && gtk_widget_has_focus(compose->focused_editable)
10229 entry_cut_clipboard(compose->focused_editable);
10232 static void compose_copy_cb(GtkAction *action, gpointer data)
10234 Compose *compose = (Compose *)data;
10235 if (compose->focused_editable
10236 #ifndef GENERIC_UMPC
10237 && gtk_widget_has_focus(compose->focused_editable)
10240 entry_copy_clipboard(compose->focused_editable);
10243 static void compose_paste_cb(GtkAction *action, gpointer data)
10245 Compose *compose = (Compose *)data;
10246 gint prev_autowrap;
10247 GtkTextBuffer *buffer;
10249 if (compose->focused_editable &&
10250 #ifndef GENERIC_UMPC
10251 gtk_widget_has_focus(compose->focused_editable)
10254 entry_paste_clipboard(compose, compose->focused_editable,
10255 prefs_common.linewrap_pastes,
10256 GDK_SELECTION_CLIPBOARD, NULL);
10261 #ifndef GENERIC_UMPC
10262 gtk_widget_has_focus(compose->text) &&
10264 compose->gtkaspell &&
10265 compose->gtkaspell->check_while_typing)
10266 gtkaspell_highlight_all(compose->gtkaspell);
10270 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10272 Compose *compose = (Compose *)data;
10273 gint wrap_quote = prefs_common.linewrap_quote;
10274 if (compose->focused_editable
10275 #ifndef GENERIC_UMPC
10276 && gtk_widget_has_focus(compose->focused_editable)
10279 /* let text_insert() (called directly or at a later time
10280 * after the gtk_editable_paste_clipboard) know that
10281 * text is to be inserted as a quotation. implemented
10282 * by using a simple refcount... */
10283 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10284 G_OBJECT(compose->focused_editable),
10285 "paste_as_quotation"));
10286 g_object_set_data(G_OBJECT(compose->focused_editable),
10287 "paste_as_quotation",
10288 GINT_TO_POINTER(paste_as_quotation + 1));
10289 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10290 entry_paste_clipboard(compose, compose->focused_editable,
10291 prefs_common.linewrap_pastes,
10292 GDK_SELECTION_CLIPBOARD, NULL);
10293 prefs_common.linewrap_quote = wrap_quote;
10297 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10299 Compose *compose = (Compose *)data;
10300 gint prev_autowrap;
10301 GtkTextBuffer *buffer;
10303 if (compose->focused_editable
10304 #ifndef GENERIC_UMPC
10305 && gtk_widget_has_focus(compose->focused_editable)
10308 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10309 GDK_SELECTION_CLIPBOARD, NULL);
10314 #ifndef GENERIC_UMPC
10315 gtk_widget_has_focus(compose->text) &&
10317 compose->gtkaspell &&
10318 compose->gtkaspell->check_while_typing)
10319 gtkaspell_highlight_all(compose->gtkaspell);
10323 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10325 Compose *compose = (Compose *)data;
10326 gint prev_autowrap;
10327 GtkTextBuffer *buffer;
10329 if (compose->focused_editable
10330 #ifndef GENERIC_UMPC
10331 && gtk_widget_has_focus(compose->focused_editable)
10334 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10335 GDK_SELECTION_CLIPBOARD, NULL);
10340 #ifndef GENERIC_UMPC
10341 gtk_widget_has_focus(compose->text) &&
10343 compose->gtkaspell &&
10344 compose->gtkaspell->check_while_typing)
10345 gtkaspell_highlight_all(compose->gtkaspell);
10349 static void compose_allsel_cb(GtkAction *action, gpointer data)
10351 Compose *compose = (Compose *)data;
10352 if (compose->focused_editable
10353 #ifndef GENERIC_UMPC
10354 && gtk_widget_has_focus(compose->focused_editable)
10357 entry_allsel(compose->focused_editable);
10360 static void textview_move_beginning_of_line (GtkTextView *text)
10362 GtkTextBuffer *buffer;
10366 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10368 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10369 mark = gtk_text_buffer_get_insert(buffer);
10370 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10371 gtk_text_iter_set_line_offset(&ins, 0);
10372 gtk_text_buffer_place_cursor(buffer, &ins);
10375 static void textview_move_forward_character (GtkTextView *text)
10377 GtkTextBuffer *buffer;
10381 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10383 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10384 mark = gtk_text_buffer_get_insert(buffer);
10385 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10386 if (gtk_text_iter_forward_cursor_position(&ins))
10387 gtk_text_buffer_place_cursor(buffer, &ins);
10390 static void textview_move_backward_character (GtkTextView *text)
10392 GtkTextBuffer *buffer;
10396 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10398 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10399 mark = gtk_text_buffer_get_insert(buffer);
10400 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10401 if (gtk_text_iter_backward_cursor_position(&ins))
10402 gtk_text_buffer_place_cursor(buffer, &ins);
10405 static void textview_move_forward_word (GtkTextView *text)
10407 GtkTextBuffer *buffer;
10412 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10414 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10415 mark = gtk_text_buffer_get_insert(buffer);
10416 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10417 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10418 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10419 gtk_text_iter_backward_word_start(&ins);
10420 gtk_text_buffer_place_cursor(buffer, &ins);
10424 static void textview_move_backward_word (GtkTextView *text)
10426 GtkTextBuffer *buffer;
10430 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10432 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10433 mark = gtk_text_buffer_get_insert(buffer);
10434 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10435 if (gtk_text_iter_backward_word_starts(&ins, 1))
10436 gtk_text_buffer_place_cursor(buffer, &ins);
10439 static void textview_move_end_of_line (GtkTextView *text)
10441 GtkTextBuffer *buffer;
10445 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10447 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10448 mark = gtk_text_buffer_get_insert(buffer);
10449 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10450 if (gtk_text_iter_forward_to_line_end(&ins))
10451 gtk_text_buffer_place_cursor(buffer, &ins);
10454 static void textview_move_next_line (GtkTextView *text)
10456 GtkTextBuffer *buffer;
10461 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10463 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10464 mark = gtk_text_buffer_get_insert(buffer);
10465 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10466 offset = gtk_text_iter_get_line_offset(&ins);
10467 if (gtk_text_iter_forward_line(&ins)) {
10468 gtk_text_iter_set_line_offset(&ins, offset);
10469 gtk_text_buffer_place_cursor(buffer, &ins);
10473 static void textview_move_previous_line (GtkTextView *text)
10475 GtkTextBuffer *buffer;
10480 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10482 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10483 mark = gtk_text_buffer_get_insert(buffer);
10484 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10485 offset = gtk_text_iter_get_line_offset(&ins);
10486 if (gtk_text_iter_backward_line(&ins)) {
10487 gtk_text_iter_set_line_offset(&ins, offset);
10488 gtk_text_buffer_place_cursor(buffer, &ins);
10492 static void textview_delete_forward_character (GtkTextView *text)
10494 GtkTextBuffer *buffer;
10496 GtkTextIter ins, end_iter;
10498 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10500 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10501 mark = gtk_text_buffer_get_insert(buffer);
10502 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10504 if (gtk_text_iter_forward_char(&end_iter)) {
10505 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10509 static void textview_delete_backward_character (GtkTextView *text)
10511 GtkTextBuffer *buffer;
10513 GtkTextIter ins, end_iter;
10515 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10517 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10518 mark = gtk_text_buffer_get_insert(buffer);
10519 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10521 if (gtk_text_iter_backward_char(&end_iter)) {
10522 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10526 static void textview_delete_forward_word (GtkTextView *text)
10528 GtkTextBuffer *buffer;
10530 GtkTextIter ins, end_iter;
10532 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10534 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10535 mark = gtk_text_buffer_get_insert(buffer);
10536 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10538 if (gtk_text_iter_forward_word_end(&end_iter)) {
10539 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10543 static void textview_delete_backward_word (GtkTextView *text)
10545 GtkTextBuffer *buffer;
10547 GtkTextIter ins, end_iter;
10549 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10551 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10552 mark = gtk_text_buffer_get_insert(buffer);
10553 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10555 if (gtk_text_iter_backward_word_start(&end_iter)) {
10556 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10560 static void textview_delete_line (GtkTextView *text)
10562 GtkTextBuffer *buffer;
10564 GtkTextIter ins, start_iter, end_iter;
10566 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10568 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10569 mark = gtk_text_buffer_get_insert(buffer);
10570 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10573 gtk_text_iter_set_line_offset(&start_iter, 0);
10576 if (gtk_text_iter_ends_line(&end_iter)){
10577 if (!gtk_text_iter_forward_char(&end_iter))
10578 gtk_text_iter_backward_char(&start_iter);
10581 gtk_text_iter_forward_to_line_end(&end_iter);
10582 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10585 static void textview_delete_to_line_end (GtkTextView *text)
10587 GtkTextBuffer *buffer;
10589 GtkTextIter ins, end_iter;
10591 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10593 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10594 mark = gtk_text_buffer_get_insert(buffer);
10595 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10597 if (gtk_text_iter_ends_line(&end_iter))
10598 gtk_text_iter_forward_char(&end_iter);
10600 gtk_text_iter_forward_to_line_end(&end_iter);
10601 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10604 #define DO_ACTION(name, act) { \
10605 if(!strcmp(name, a_name)) { \
10609 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10611 const gchar *a_name = gtk_action_get_name(action);
10612 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10613 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10614 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10615 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10616 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10617 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10618 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10619 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10620 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10621 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10622 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10623 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10624 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10625 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10629 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10631 Compose *compose = (Compose *)data;
10632 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10633 ComposeCallAdvancedAction action = -1;
10635 action = compose_call_advanced_action_from_path(gaction);
10638 void (*do_action) (GtkTextView *text);
10639 } action_table[] = {
10640 {textview_move_beginning_of_line},
10641 {textview_move_forward_character},
10642 {textview_move_backward_character},
10643 {textview_move_forward_word},
10644 {textview_move_backward_word},
10645 {textview_move_end_of_line},
10646 {textview_move_next_line},
10647 {textview_move_previous_line},
10648 {textview_delete_forward_character},
10649 {textview_delete_backward_character},
10650 {textview_delete_forward_word},
10651 {textview_delete_backward_word},
10652 {textview_delete_line},
10653 {textview_delete_to_line_end}
10656 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10658 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10659 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10660 if (action_table[action].do_action)
10661 action_table[action].do_action(text);
10663 g_warning("Not implemented yet.");
10667 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10669 GtkAllocation allocation;
10673 if (GTK_IS_EDITABLE(widget)) {
10674 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10675 gtk_editable_set_position(GTK_EDITABLE(widget),
10678 if ((parent = gtk_widget_get_parent(widget))
10679 && (parent = gtk_widget_get_parent(parent))
10680 && (parent = gtk_widget_get_parent(parent))) {
10681 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10682 gtk_widget_get_allocation(widget, &allocation);
10683 gint y = allocation.y;
10684 gint height = allocation.height;
10685 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10686 (GTK_SCROLLED_WINDOW(parent));
10688 gfloat value = gtk_adjustment_get_value(shown);
10689 gfloat upper = gtk_adjustment_get_upper(shown);
10690 gfloat page_size = gtk_adjustment_get_page_size(shown);
10691 if (y < (int)value) {
10692 gtk_adjustment_set_value(shown, y - 1);
10694 if ((y + height) > ((int)value + (int)page_size)) {
10695 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10696 gtk_adjustment_set_value(shown,
10697 y + height - (int)page_size - 1);
10699 gtk_adjustment_set_value(shown,
10700 (int)upper - (int)page_size - 1);
10707 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10708 compose->focused_editable = widget;
10710 #ifdef GENERIC_UMPC
10711 if (GTK_IS_TEXT_VIEW(widget)
10712 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10713 g_object_ref(compose->notebook);
10714 g_object_ref(compose->edit_vbox);
10715 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10716 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10717 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10718 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10719 g_object_unref(compose->notebook);
10720 g_object_unref(compose->edit_vbox);
10721 g_signal_handlers_block_by_func(G_OBJECT(widget),
10722 G_CALLBACK(compose_grab_focus_cb),
10724 gtk_widget_grab_focus(widget);
10725 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10726 G_CALLBACK(compose_grab_focus_cb),
10728 } else if (!GTK_IS_TEXT_VIEW(widget)
10729 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10730 g_object_ref(compose->notebook);
10731 g_object_ref(compose->edit_vbox);
10732 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10733 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10734 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10735 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10736 g_object_unref(compose->notebook);
10737 g_object_unref(compose->edit_vbox);
10738 g_signal_handlers_block_by_func(G_OBJECT(widget),
10739 G_CALLBACK(compose_grab_focus_cb),
10741 gtk_widget_grab_focus(widget);
10742 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10743 G_CALLBACK(compose_grab_focus_cb),
10749 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10751 compose->modified = TRUE;
10752 // compose_beautify_paragraph(compose, NULL, TRUE);
10753 #ifndef GENERIC_UMPC
10754 compose_set_title(compose);
10758 static void compose_wrap_cb(GtkAction *action, gpointer data)
10760 Compose *compose = (Compose *)data;
10761 compose_beautify_paragraph(compose, NULL, TRUE);
10764 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10766 Compose *compose = (Compose *)data;
10767 compose_wrap_all_full(compose, TRUE);
10770 static void compose_find_cb(GtkAction *action, gpointer data)
10772 Compose *compose = (Compose *)data;
10774 message_search_compose(compose);
10777 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10780 Compose *compose = (Compose *)data;
10781 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10782 if (compose->autowrap)
10783 compose_wrap_all_full(compose, TRUE);
10784 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10787 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10790 Compose *compose = (Compose *)data;
10791 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10794 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10796 Compose *compose = (Compose *)data;
10798 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10801 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10803 Compose *compose = (Compose *)data;
10805 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10808 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10810 g_free(compose->privacy_system);
10812 compose->privacy_system = g_strdup(account->default_privacy_system);
10813 compose_update_privacy_system_menu_item(compose, warn);
10816 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10818 Compose *compose = (Compose *)data;
10820 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10821 gtk_widget_show(compose->ruler_hbox);
10822 prefs_common.show_ruler = TRUE;
10824 gtk_widget_hide(compose->ruler_hbox);
10825 gtk_widget_queue_resize(compose->edit_vbox);
10826 prefs_common.show_ruler = FALSE;
10830 static void compose_attach_drag_received_cb (GtkWidget *widget,
10831 GdkDragContext *context,
10834 GtkSelectionData *data,
10837 gpointer user_data)
10839 Compose *compose = (Compose *)user_data;
10843 type = gtk_selection_data_get_data_type(data);
10844 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10846 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10848 ) && gtk_drag_get_source_widget(context) !=
10849 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10850 list = uri_list_extract_filenames(
10851 (const gchar *)gtk_selection_data_get_data(data));
10852 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10853 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10854 compose_attach_append
10855 (compose, (const gchar *)tmp->data,
10856 utf8_filename, NULL, NULL);
10857 g_free(utf8_filename);
10859 if (list) compose_changed_cb(NULL, compose);
10860 list_free_strings(list);
10862 } else if (gtk_drag_get_source_widget(context)
10863 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10864 /* comes from our summaryview */
10865 SummaryView * summaryview = NULL;
10866 GSList * list = NULL, *cur = NULL;
10868 if (mainwindow_get_mainwindow())
10869 summaryview = mainwindow_get_mainwindow()->summaryview;
10872 list = summary_get_selected_msg_list(summaryview);
10874 for (cur = list; cur; cur = cur->next) {
10875 MsgInfo *msginfo = (MsgInfo *)cur->data;
10876 gchar *file = NULL;
10878 file = procmsg_get_message_file_full(msginfo,
10881 compose_attach_append(compose, (const gchar *)file,
10882 (const gchar *)file, "message/rfc822", NULL);
10886 g_slist_free(list);
10890 static gboolean compose_drag_drop(GtkWidget *widget,
10891 GdkDragContext *drag_context,
10893 guint time, gpointer user_data)
10895 /* not handling this signal makes compose_insert_drag_received_cb
10900 static gboolean completion_set_focus_to_subject
10901 (GtkWidget *widget,
10902 GdkEventKey *event,
10905 cm_return_val_if_fail(compose != NULL, FALSE);
10907 /* make backtab move to subject field */
10908 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10909 gtk_widget_grab_focus(compose->subject_entry);
10915 static void compose_insert_drag_received_cb (GtkWidget *widget,
10916 GdkDragContext *drag_context,
10919 GtkSelectionData *data,
10922 gpointer user_data)
10924 Compose *compose = (Compose *)user_data;
10928 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10930 type = gtk_selection_data_get_data_type(data);
10932 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10934 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10936 AlertValue val = G_ALERTDEFAULT;
10937 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10939 list = uri_list_extract_filenames(ddata);
10940 if (list == NULL && strstr(ddata, "://")) {
10941 /* Assume a list of no files, and data has ://, is a remote link */
10942 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10943 gchar *tmpfile = get_tmp_file();
10944 str_write_to_file(tmpdata, tmpfile);
10946 compose_insert_file(compose, tmpfile);
10947 claws_unlink(tmpfile);
10949 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10950 compose_beautify_paragraph(compose, NULL, TRUE);
10953 switch (prefs_common.compose_dnd_mode) {
10954 case COMPOSE_DND_ASK:
10955 val = alertpanel_full(_("Insert or attach?"),
10956 _("Do you want to insert the contents of the file(s) "
10957 "into the message body, or attach it to the email?"),
10958 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10959 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10961 case COMPOSE_DND_INSERT:
10962 val = G_ALERTALTERNATE;
10964 case COMPOSE_DND_ATTACH:
10965 val = G_ALERTOTHER;
10968 /* unexpected case */
10969 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10972 if (val & G_ALERTDISABLE) {
10973 val &= ~G_ALERTDISABLE;
10974 /* remember what action to perform by default, only if we don't click Cancel */
10975 if (val == G_ALERTALTERNATE)
10976 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10977 else if (val == G_ALERTOTHER)
10978 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10981 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10982 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10983 list_free_strings(list);
10986 } else if (val == G_ALERTOTHER) {
10987 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10988 list_free_strings(list);
10993 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10994 compose_insert_file(compose, (const gchar *)tmp->data);
10996 list_free_strings(list);
10998 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11003 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11006 static void compose_header_drag_received_cb (GtkWidget *widget,
11007 GdkDragContext *drag_context,
11010 GtkSelectionData *data,
11013 gpointer user_data)
11015 GtkEditable *entry = (GtkEditable *)user_data;
11016 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11018 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11021 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11022 gchar *decoded=g_new(gchar, strlen(email));
11025 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11026 gtk_editable_delete_text(entry, 0, -1);
11027 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11028 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11032 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11035 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11037 Compose *compose = (Compose *)data;
11039 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11040 compose->return_receipt = TRUE;
11042 compose->return_receipt = FALSE;
11045 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11047 Compose *compose = (Compose *)data;
11049 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11050 compose->remove_references = TRUE;
11052 compose->remove_references = FALSE;
11055 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11056 ComposeHeaderEntry *headerentry)
11058 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11062 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11063 GdkEventKey *event,
11064 ComposeHeaderEntry *headerentry)
11066 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11067 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11068 !(event->state & GDK_MODIFIER_MASK) &&
11069 (event->keyval == GDK_KEY_BackSpace) &&
11070 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11071 gtk_container_remove
11072 (GTK_CONTAINER(headerentry->compose->header_table),
11073 headerentry->combo);
11074 gtk_container_remove
11075 (GTK_CONTAINER(headerentry->compose->header_table),
11076 headerentry->entry);
11077 headerentry->compose->header_list =
11078 g_slist_remove(headerentry->compose->header_list,
11080 g_free(headerentry);
11081 } else if (event->keyval == GDK_KEY_Tab) {
11082 if (headerentry->compose->header_last == headerentry) {
11083 /* Override default next focus, and give it to subject_entry
11084 * instead of notebook tabs
11086 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11087 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11094 static gboolean scroll_postpone(gpointer data)
11096 Compose *compose = (Compose *)data;
11098 cm_return_val_if_fail(!compose->batch, FALSE);
11100 GTK_EVENTS_FLUSH();
11101 compose_show_first_last_header(compose, FALSE);
11105 static void compose_headerentry_changed_cb(GtkWidget *entry,
11106 ComposeHeaderEntry *headerentry)
11108 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11109 compose_create_header_entry(headerentry->compose);
11110 g_signal_handlers_disconnect_matched
11111 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11112 0, 0, NULL, NULL, headerentry);
11114 if (!headerentry->compose->batch)
11115 g_timeout_add(0, scroll_postpone, headerentry->compose);
11119 static gboolean compose_defer_auto_save_draft(Compose *compose)
11121 compose->draft_timeout_tag = -1;
11122 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11126 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11128 GtkAdjustment *vadj;
11130 cm_return_if_fail(compose);
11131 cm_return_if_fail(!compose->batch);
11132 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11133 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11134 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11135 gtk_widget_get_parent(compose->header_table)));
11136 gtk_adjustment_set_value(vadj, (show_first ?
11137 gtk_adjustment_get_lower(vadj) :
11138 (gtk_adjustment_get_upper(vadj) -
11139 gtk_adjustment_get_page_size(vadj))));
11140 gtk_adjustment_changed(vadj);
11143 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11144 const gchar *text, gint len, Compose *compose)
11146 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11147 (G_OBJECT(compose->text), "paste_as_quotation"));
11150 cm_return_if_fail(text != NULL);
11152 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11153 G_CALLBACK(text_inserted),
11155 if (paste_as_quotation) {
11157 const gchar *qmark;
11159 GtkTextIter start_iter;
11162 len = strlen(text);
11164 new_text = g_strndup(text, len);
11166 qmark = compose_quote_char_from_context(compose);
11168 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11169 gtk_text_buffer_place_cursor(buffer, iter);
11171 pos = gtk_text_iter_get_offset(iter);
11173 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11174 _("Quote format error at line %d."));
11175 quote_fmt_reset_vartable();
11177 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11178 GINT_TO_POINTER(paste_as_quotation - 1));
11180 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11181 gtk_text_buffer_place_cursor(buffer, iter);
11182 gtk_text_buffer_delete_mark(buffer, mark);
11184 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11185 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11186 compose_beautify_paragraph(compose, &start_iter, FALSE);
11187 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11188 gtk_text_buffer_delete_mark(buffer, mark);
11190 if (strcmp(text, "\n") || compose->automatic_break
11191 || gtk_text_iter_starts_line(iter)) {
11192 GtkTextIter before_ins;
11193 gtk_text_buffer_insert(buffer, iter, text, len);
11194 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11195 before_ins = *iter;
11196 gtk_text_iter_backward_chars(&before_ins, len);
11197 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11200 /* check if the preceding is just whitespace or quote */
11201 GtkTextIter start_line;
11202 gchar *tmp = NULL, *quote = NULL;
11203 gint quote_len = 0, is_normal = 0;
11204 start_line = *iter;
11205 gtk_text_iter_set_line_offset(&start_line, 0);
11206 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11209 if (*tmp == '\0') {
11212 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11220 gtk_text_buffer_insert(buffer, iter, text, len);
11222 gtk_text_buffer_insert_with_tags_by_name(buffer,
11223 iter, text, len, "no_join", NULL);
11228 if (!paste_as_quotation) {
11229 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11230 compose_beautify_paragraph(compose, iter, FALSE);
11231 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11232 gtk_text_buffer_delete_mark(buffer, mark);
11235 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11236 G_CALLBACK(text_inserted),
11238 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11240 if (prefs_common.autosave &&
11241 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11242 compose->draft_timeout_tag != -2 /* disabled while loading */)
11243 compose->draft_timeout_tag = g_timeout_add
11244 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11248 static void compose_check_all(GtkAction *action, gpointer data)
11250 Compose *compose = (Compose *)data;
11251 if (!compose->gtkaspell)
11254 if (gtk_widget_has_focus(compose->subject_entry))
11255 claws_spell_entry_check_all(
11256 CLAWS_SPELL_ENTRY(compose->subject_entry));
11258 gtkaspell_check_all(compose->gtkaspell);
11261 static void compose_highlight_all(GtkAction *action, gpointer data)
11263 Compose *compose = (Compose *)data;
11264 if (compose->gtkaspell) {
11265 claws_spell_entry_recheck_all(
11266 CLAWS_SPELL_ENTRY(compose->subject_entry));
11267 gtkaspell_highlight_all(compose->gtkaspell);
11271 static void compose_check_backwards(GtkAction *action, gpointer data)
11273 Compose *compose = (Compose *)data;
11274 if (!compose->gtkaspell) {
11275 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11279 if (gtk_widget_has_focus(compose->subject_entry))
11280 claws_spell_entry_check_backwards(
11281 CLAWS_SPELL_ENTRY(compose->subject_entry));
11283 gtkaspell_check_backwards(compose->gtkaspell);
11286 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11288 Compose *compose = (Compose *)data;
11289 if (!compose->gtkaspell) {
11290 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11294 if (gtk_widget_has_focus(compose->subject_entry))
11295 claws_spell_entry_check_forwards_go(
11296 CLAWS_SPELL_ENTRY(compose->subject_entry));
11298 gtkaspell_check_forwards_go(compose->gtkaspell);
11303 *\brief Guess originating forward account from MsgInfo and several
11304 * "common preference" settings. Return NULL if no guess.
11306 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11308 PrefsAccount *account = NULL;
11310 cm_return_val_if_fail(msginfo, NULL);
11311 cm_return_val_if_fail(msginfo->folder, NULL);
11312 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11314 if (msginfo->folder->prefs->enable_default_account)
11315 account = account_find_from_id(msginfo->folder->prefs->default_account);
11318 account = msginfo->folder->folder->account;
11320 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11322 Xstrdup_a(to, msginfo->to, return NULL);
11323 extract_address(to);
11324 account = account_find_from_address(to, FALSE);
11327 if (!account && prefs_common.forward_account_autosel) {
11328 gchar cc[BUFFSIZE];
11329 if (!procheader_get_header_from_msginfo
11330 (msginfo, cc,sizeof cc , "Cc:")) {
11331 gchar *buf = cc + strlen("Cc:");
11332 extract_address(buf);
11333 account = account_find_from_address(buf, FALSE);
11337 if (!account && prefs_common.forward_account_autosel) {
11338 gchar deliveredto[BUFFSIZE];
11339 if (!procheader_get_header_from_msginfo
11340 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11341 gchar *buf = deliveredto + strlen("Delivered-To:");
11342 extract_address(buf);
11343 account = account_find_from_address(buf, FALSE);
11350 gboolean compose_close(Compose *compose)
11354 if (!g_mutex_trylock(compose->mutex)) {
11355 /* we have to wait for the (possibly deferred by auto-save)
11356 * drafting to be done, before destroying the compose under
11358 debug_print("waiting for drafting to finish...\n");
11359 compose_allow_user_actions(compose, FALSE);
11360 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11363 cm_return_val_if_fail(compose, FALSE);
11364 gtkut_widget_get_uposition(compose->window, &x, &y);
11365 if (!compose->batch) {
11366 prefs_common.compose_x = x;
11367 prefs_common.compose_y = y;
11369 g_mutex_unlock(compose->mutex);
11370 compose_destroy(compose);
11375 * Add entry field for each address in list.
11376 * \param compose E-Mail composition object.
11377 * \param listAddress List of (formatted) E-Mail addresses.
11379 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11382 node = listAddress;
11384 addr = ( gchar * ) node->data;
11385 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11386 node = g_list_next( node );
11390 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11391 guint action, gboolean opening_multiple)
11393 gchar *body = NULL;
11394 GSList *new_msglist = NULL;
11395 MsgInfo *tmp_msginfo = NULL;
11396 gboolean originally_enc = FALSE;
11397 gboolean originally_sig = FALSE;
11398 Compose *compose = NULL;
11399 gchar *s_system = NULL;
11401 cm_return_if_fail(msgview != NULL);
11403 cm_return_if_fail(msginfo_list != NULL);
11405 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11406 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11407 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11409 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11410 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11411 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11412 orig_msginfo, mimeinfo);
11413 if (tmp_msginfo != NULL) {
11414 new_msglist = g_slist_append(NULL, tmp_msginfo);
11416 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11417 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11418 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11420 tmp_msginfo->folder = orig_msginfo->folder;
11421 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11422 if (orig_msginfo->tags) {
11423 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11424 tmp_msginfo->folder->tags_dirty = TRUE;
11430 if (!opening_multiple)
11431 body = messageview_get_selection(msgview);
11434 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11435 procmsg_msginfo_free(tmp_msginfo);
11436 g_slist_free(new_msglist);
11438 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11440 if (compose && originally_enc) {
11441 compose_force_encryption(compose, compose->account, FALSE, s_system);
11444 if (compose && originally_sig && compose->account->default_sign_reply) {
11445 compose_force_signing(compose, compose->account, s_system);
11449 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11452 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11455 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11456 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11457 GSList *cur = msginfo_list;
11458 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11459 "messages. Opening the windows "
11460 "could take some time. Do you "
11461 "want to continue?"),
11462 g_slist_length(msginfo_list));
11463 if (g_slist_length(msginfo_list) > 9
11464 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11465 != G_ALERTALTERNATE) {
11470 /* We'll open multiple compose windows */
11471 /* let the WM place the next windows */
11472 compose_force_window_origin = FALSE;
11473 for (; cur; cur = cur->next) {
11475 tmplist.data = cur->data;
11476 tmplist.next = NULL;
11477 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11479 compose_force_window_origin = TRUE;
11481 /* forwarding multiple mails as attachments is done via a
11482 * single compose window */
11483 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11487 void compose_check_for_email_account(Compose *compose)
11489 PrefsAccount *ac = NULL, *curr = NULL;
11495 if (compose->account && compose->account->protocol == A_NNTP) {
11496 ac = account_get_cur_account();
11497 if (ac->protocol == A_NNTP) {
11498 list = account_get_list();
11500 for( ; list != NULL ; list = g_list_next(list)) {
11501 curr = (PrefsAccount *) list->data;
11502 if (curr->protocol != A_NNTP) {
11508 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11513 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11514 const gchar *address)
11516 GSList *msginfo_list = NULL;
11517 gchar *body = messageview_get_selection(msgview);
11520 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11522 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11523 compose_check_for_email_account(compose);
11524 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11525 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11526 compose_reply_set_subject(compose, msginfo);
11529 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11532 void compose_set_position(Compose *compose, gint pos)
11534 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11536 gtkut_text_view_set_position(text, pos);
11539 gboolean compose_search_string(Compose *compose,
11540 const gchar *str, gboolean case_sens)
11542 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11544 return gtkut_text_view_search_string(text, str, case_sens);
11547 gboolean compose_search_string_backward(Compose *compose,
11548 const gchar *str, gboolean case_sens)
11550 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11552 return gtkut_text_view_search_string_backward(text, str, case_sens);
11555 /* allocate a msginfo structure and populate its data from a compose data structure */
11556 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11558 MsgInfo *newmsginfo;
11560 gchar buf[BUFFSIZE];
11562 cm_return_val_if_fail( compose != NULL, NULL );
11564 newmsginfo = procmsg_msginfo_new();
11567 get_rfc822_date(buf, sizeof(buf));
11568 newmsginfo->date = g_strdup(buf);
11571 if (compose->from_name) {
11572 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11573 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11577 if (compose->subject_entry)
11578 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11580 /* to, cc, reply-to, newsgroups */
11581 for (list = compose->header_list; list; list = list->next) {
11582 gchar *header = gtk_editable_get_chars(
11584 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11585 gchar *entry = gtk_editable_get_chars(
11586 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11588 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11589 if ( newmsginfo->to == NULL ) {
11590 newmsginfo->to = g_strdup(entry);
11591 } else if (entry && *entry) {
11592 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11593 g_free(newmsginfo->to);
11594 newmsginfo->to = tmp;
11597 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11598 if ( newmsginfo->cc == NULL ) {
11599 newmsginfo->cc = g_strdup(entry);
11600 } else if (entry && *entry) {
11601 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11602 g_free(newmsginfo->cc);
11603 newmsginfo->cc = tmp;
11606 if ( strcasecmp(header,
11607 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11608 if ( newmsginfo->newsgroups == NULL ) {
11609 newmsginfo->newsgroups = g_strdup(entry);
11610 } else if (entry && *entry) {
11611 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11612 g_free(newmsginfo->newsgroups);
11613 newmsginfo->newsgroups = tmp;
11621 /* other data is unset */
11627 /* update compose's dictionaries from folder dict settings */
11628 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11629 FolderItem *folder_item)
11631 cm_return_if_fail(compose != NULL);
11633 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11634 FolderItemPrefs *prefs = folder_item->prefs;
11636 if (prefs->enable_default_dictionary)
11637 gtkaspell_change_dict(compose->gtkaspell,
11638 prefs->default_dictionary, FALSE);
11639 if (folder_item->prefs->enable_default_alt_dictionary)
11640 gtkaspell_change_alt_dict(compose->gtkaspell,
11641 prefs->default_alt_dictionary);
11642 if (prefs->enable_default_dictionary
11643 || prefs->enable_default_alt_dictionary)
11644 compose_spell_menu_changed(compose);
11649 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11651 Compose *compose = (Compose *)data;
11653 cm_return_if_fail(compose != NULL);
11655 gtk_widget_grab_focus(compose->text);