2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
397 static void compose_close_cb (GtkAction *action,
399 static void compose_print_cb (GtkAction *action,
402 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
404 static void compose_address_cb (GtkAction *action,
406 static void about_show_cb (GtkAction *action,
408 static void compose_template_activate_cb(GtkWidget *widget,
411 static void compose_ext_editor_cb (GtkAction *action,
414 static gint compose_delete_cb (GtkWidget *widget,
418 static void compose_undo_cb (GtkAction *action,
420 static void compose_redo_cb (GtkAction *action,
422 static void compose_cut_cb (GtkAction *action,
424 static void compose_copy_cb (GtkAction *action,
426 static void compose_paste_cb (GtkAction *action,
428 static void compose_paste_as_quote_cb (GtkAction *action,
430 static void compose_paste_no_wrap_cb (GtkAction *action,
432 static void compose_paste_wrap_cb (GtkAction *action,
434 static void compose_allsel_cb (GtkAction *action,
437 static void compose_advanced_action_cb (GtkAction *action,
440 static void compose_grab_focus_cb (GtkWidget *widget,
443 static void compose_changed_cb (GtkTextBuffer *textbuf,
446 static void compose_wrap_cb (GtkAction *action,
448 static void compose_wrap_all_cb (GtkAction *action,
450 static void compose_find_cb (GtkAction *action,
452 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
454 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
457 static void compose_toggle_ruler_cb (GtkToggleAction *action,
459 static void compose_toggle_sign_cb (GtkToggleAction *action,
461 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
463 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
472 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
474 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
475 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
476 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
478 static void compose_attach_drag_received_cb (GtkWidget *widget,
479 GdkDragContext *drag_context,
482 GtkSelectionData *data,
486 static void compose_insert_drag_received_cb (GtkWidget *widget,
487 GdkDragContext *drag_context,
490 GtkSelectionData *data,
494 static void compose_header_drag_received_cb (GtkWidget *widget,
495 GdkDragContext *drag_context,
498 GtkSelectionData *data,
503 static gboolean compose_drag_drop (GtkWidget *widget,
504 GdkDragContext *drag_context,
506 guint time, gpointer user_data);
507 static gboolean completion_set_focus_to_subject
512 static void text_inserted (GtkTextBuffer *buffer,
517 static Compose *compose_generic_reply(MsgInfo *msginfo,
518 ComposeQuoteMode quote_mode,
522 gboolean followup_and_reply_to,
525 static void compose_headerentry_changed_cb (GtkWidget *entry,
526 ComposeHeaderEntry *headerentry);
527 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
529 ComposeHeaderEntry *headerentry);
530 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
531 ComposeHeaderEntry *headerentry);
533 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
535 static void compose_allow_user_actions (Compose *compose, gboolean allow);
537 static void compose_nothing_cb (GtkAction *action, gpointer data)
543 static void compose_check_all (GtkAction *action, gpointer data);
544 static void compose_highlight_all (GtkAction *action, gpointer data);
545 static void compose_check_backwards (GtkAction *action, gpointer data);
546 static void compose_check_forwards_go (GtkAction *action, gpointer data);
549 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
551 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
554 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
555 FolderItem *folder_item);
557 static void compose_attach_update_label(Compose *compose);
558 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
559 gboolean respect_default_to);
560 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
562 static GtkActionEntry compose_popup_entries[] =
564 {"Compose", NULL, "Compose" },
565 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
566 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
567 {"Compose/---", NULL, "---", NULL, NULL, NULL },
568 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
571 static GtkActionEntry compose_entries[] =
573 {"Menu", NULL, "Menu" },
575 {"Message", NULL, N_("_Message") },
576 {"Edit", NULL, N_("_Edit") },
578 {"Spelling", NULL, N_("_Spelling") },
580 {"Options", NULL, N_("_Options") },
581 {"Tools", NULL, N_("_Tools") },
582 {"Help", NULL, N_("_Help") },
584 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
585 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
586 {"Message/---", NULL, "---" },
588 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
589 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
590 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
591 /* {"Message/---", NULL, "---" }, */
592 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
593 /* {"Message/---", NULL, "---" }, */
594 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
595 /* {"Message/---", NULL, "---" }, */
596 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
599 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
600 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
601 {"Edit/---", NULL, "---" },
603 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
604 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
605 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
607 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
608 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
609 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
610 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
612 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
614 {"Edit/Advanced", NULL, N_("A_dvanced") },
615 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
616 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
617 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
618 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
619 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
620 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
621 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
622 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
623 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
624 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
625 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
626 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
627 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
628 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
630 /* {"Edit/---", NULL, "---" }, */
631 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
633 /* {"Edit/---", NULL, "---" }, */
634 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
635 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
640 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
641 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
642 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
643 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
645 {"Spelling/---", NULL, "---" },
646 {"Spelling/Options", NULL, N_("_Options") },
651 {"Options/ReplyMode", NULL, N_("Reply _mode") },
652 {"Options/---", NULL, "---" },
653 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
654 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
656 /* {"Options/---", NULL, "---" }, */
658 {"Options/Priority", NULL, N_("_Priority") },
660 {"Options/Encoding", NULL, N_("Character _encoding") },
661 {"Options/Encoding/---", NULL, "---" },
662 #define ENC_ACTION(cs_char,c_char,string) \
663 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
665 {"Options/Encoding/Western", NULL, N_("Western European") },
666 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
667 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
668 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
669 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
670 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
671 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
672 {"Options/Encoding/Korean", NULL, N_("Korean") },
673 {"Options/Encoding/Thai", NULL, N_("Thai") },
676 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
678 {"Tools/Template", NULL, N_("_Template") },
679 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
680 {"Tools/Actions", NULL, N_("Actio_ns") },
681 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
687 static GtkToggleActionEntry compose_toggle_entries[] =
689 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
690 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
691 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
692 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
693 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
694 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
695 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
698 static GtkRadioActionEntry compose_radio_rm_entries[] =
700 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
701 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
702 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
703 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
706 static GtkRadioActionEntry compose_radio_prio_entries[] =
708 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
709 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
710 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
711 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
712 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
715 static GtkRadioActionEntry compose_radio_enc_entries[] =
717 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
751 static GtkTargetEntry compose_mime_types[] =
753 {"text/uri-list", 0, 0},
754 {"UTF8_STRING", 0, 0},
758 static gboolean compose_put_existing_to_front(MsgInfo *info)
760 GList *compose_list = compose_get_compose_list();
764 for (elem = compose_list; elem != NULL && elem->data != NULL;
766 Compose *c = (Compose*)elem->data;
768 if (!c->targetinfo || !c->targetinfo->msgid ||
772 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
773 gtkut_window_popup(c->window);
781 static GdkColor quote_color1 =
782 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
783 static GdkColor quote_color2 =
784 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
785 static GdkColor quote_color3 =
786 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_bgcolor1 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 static GdkColor quote_bgcolor2 =
791 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
792 static GdkColor quote_bgcolor3 =
793 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor signature_color = {
802 static GdkColor uri_color = {
809 static void compose_create_tags(GtkTextView *text, Compose *compose)
811 GtkTextBuffer *buffer;
812 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 #if !GTK_CHECK_VERSION(2, 24, 0)
820 buffer = gtk_text_view_get_buffer(text);
822 if (prefs_common.enable_color) {
823 /* grab the quote colors, converting from an int to a GdkColor */
824 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
826 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
828 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
836 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
838 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
841 signature_color = quote_color1 = quote_color2 = quote_color3 =
842 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
845 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
846 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
847 "foreground-gdk", "e_color1,
848 "paragraph-background-gdk", "e_bgcolor1,
850 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
851 "foreground-gdk", "e_color2,
852 "paragraph-background-gdk", "e_bgcolor2,
854 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
855 "foreground-gdk", "e_color3,
856 "paragraph-background-gdk", "e_bgcolor3,
859 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
860 "foreground-gdk", "e_color1,
862 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
863 "foreground-gdk", "e_color2,
865 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
866 "foreground-gdk", "e_color3,
870 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
871 "foreground-gdk", &signature_color,
874 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
875 "foreground-gdk", &uri_color,
877 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
878 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
880 #if !GTK_CHECK_VERSION(2, 24, 0)
881 color[0] = quote_color1;
882 color[1] = quote_color2;
883 color[2] = quote_color3;
884 color[3] = quote_bgcolor1;
885 color[4] = quote_bgcolor2;
886 color[5] = quote_bgcolor3;
887 color[6] = signature_color;
888 color[7] = uri_color;
890 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
891 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
893 for (i = 0; i < 8; i++) {
894 if (success[i] == FALSE) {
895 g_warning("Compose: color allocation failed.\n");
896 quote_color1 = quote_color2 = quote_color3 =
897 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
898 signature_color = uri_color = black;
904 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
910 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
912 return compose_generic_new(account, mailto, item, NULL, NULL);
915 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
917 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
920 #define SCROLL_TO_CURSOR(compose) { \
921 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
922 gtk_text_view_get_buffer( \
923 GTK_TEXT_VIEW(compose->text))); \
924 gtk_text_view_scroll_mark_onscreen( \
925 GTK_TEXT_VIEW(compose->text), \
929 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
932 if (folderidentifier) {
933 #if !GTK_CHECK_VERSION(2, 24, 0)
934 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
936 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
938 prefs_common.compose_save_to_history = add_history(
939 prefs_common.compose_save_to_history, folderidentifier);
940 #if !GTK_CHECK_VERSION(2, 24, 0)
941 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
942 prefs_common.compose_save_to_history);
944 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
945 prefs_common.compose_save_to_history);
949 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
950 if (folderidentifier)
951 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
953 gtk_entry_set_text(GTK_ENTRY(entry), "");
956 static gchar *compose_get_save_to(Compose *compose)
959 gchar *result = NULL;
960 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
961 result = gtk_editable_get_chars(entry, 0, -1);
964 #if !GTK_CHECK_VERSION(2, 24, 0)
965 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
967 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo));
969 prefs_common.compose_save_to_history = add_history(
970 prefs_common.compose_save_to_history, result);
971 #if !GTK_CHECK_VERSION(2, 24, 0)
972 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
973 prefs_common.compose_save_to_history);
975 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose->savemsg_combo),
976 prefs_common.compose_save_to_history);
982 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
983 GList *attach_files, GList *listAddress )
986 GtkTextView *textview;
987 GtkTextBuffer *textbuf;
989 const gchar *subject_format = NULL;
990 const gchar *body_format = NULL;
991 gchar *mailto_from = NULL;
992 PrefsAccount *mailto_account = NULL;
993 MsgInfo* dummyinfo = NULL;
994 gint cursor_pos = -1;
995 MailField mfield = NO_FIELD_PRESENT;
999 /* check if mailto defines a from */
1000 if (mailto && *mailto != '\0') {
1001 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
1002 /* mailto defines a from, check if we can get account prefs from it,
1003 if not, the account prefs will be guessed using other ways, but we'll keep
1006 mailto_account = account_find_from_address(mailto_from, TRUE);
1007 if (mailto_account == NULL) {
1009 Xstrdup_a(tmp_from, mailto_from, return NULL);
1010 extract_address(tmp_from);
1011 mailto_account = account_find_from_address(tmp_from, TRUE);
1015 account = mailto_account;
1018 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1019 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1020 account = account_find_from_id(item->prefs->default_account);
1022 /* if no account prefs set, fallback to the current one */
1023 if (!account) account = cur_account;
1024 cm_return_val_if_fail(account != NULL, NULL);
1026 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1028 /* override from name if mailto asked for it */
1030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1031 g_free(mailto_from);
1033 /* override from name according to folder properties */
1034 if (item && item->prefs &&
1035 item->prefs->compose_with_format &&
1036 item->prefs->compose_override_from_format &&
1037 *item->prefs->compose_override_from_format != '\0') {
1042 dummyinfo = compose_msginfo_new_from_compose(compose);
1044 /* decode \-escape sequences in the internal representation of the quote format */
1045 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1046 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1049 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1050 compose->gtkaspell);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1054 quote_fmt_scan_string(tmp);
1057 buf = quote_fmt_get_buffer();
1059 alertpanel_error(_("New message From format error."));
1061 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1062 quote_fmt_reset_vartable();
1067 compose->replyinfo = NULL;
1068 compose->fwdinfo = NULL;
1070 textview = GTK_TEXT_VIEW(compose->text);
1071 textbuf = gtk_text_view_get_buffer(textview);
1072 compose_create_tags(textview, compose);
1074 undo_block(compose->undostruct);
1076 compose_set_dictionaries_from_folder_prefs(compose, item);
1079 if (account->auto_sig)
1080 compose_insert_sig(compose, FALSE);
1081 gtk_text_buffer_get_start_iter(textbuf, &iter);
1082 gtk_text_buffer_place_cursor(textbuf, &iter);
1084 if (account->protocol != A_NNTP) {
1085 if (mailto && *mailto != '\0') {
1086 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1089 compose_set_folder_prefs(compose, item, TRUE);
1091 if (item && item->ret_rcpt) {
1092 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1095 if (mailto && *mailto != '\0') {
1096 if (!strchr(mailto, '@'))
1097 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1099 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1100 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1101 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1102 mfield = TO_FIELD_PRESENT;
1105 * CLAWS: just don't allow return receipt request, even if the user
1106 * may want to send an email. simple but foolproof.
1108 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1110 compose_add_field_list( compose, listAddress );
1112 if (item && item->prefs && item->prefs->compose_with_format) {
1113 subject_format = item->prefs->compose_subject_format;
1114 body_format = item->prefs->compose_body_format;
1115 } else if (account->compose_with_format) {
1116 subject_format = account->compose_subject_format;
1117 body_format = account->compose_body_format;
1118 } else if (prefs_common.compose_with_format) {
1119 subject_format = prefs_common.compose_subject_format;
1120 body_format = prefs_common.compose_body_format;
1123 if (subject_format || body_format) {
1126 && *subject_format != '\0' )
1128 gchar *subject = NULL;
1133 dummyinfo = compose_msginfo_new_from_compose(compose);
1135 /* decode \-escape sequences in the internal representation of the quote format */
1136 tmp = g_malloc(strlen(subject_format)+1);
1137 pref_get_unescaped_pref(tmp, subject_format);
1139 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1141 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1142 compose->gtkaspell);
1144 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1146 quote_fmt_scan_string(tmp);
1149 buf = quote_fmt_get_buffer();
1151 alertpanel_error(_("New message subject format error."));
1153 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1154 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1155 quote_fmt_reset_vartable();
1159 mfield = SUBJECT_FIELD_PRESENT;
1163 && *body_format != '\0' )
1166 GtkTextBuffer *buffer;
1167 GtkTextIter start, end;
1171 dummyinfo = compose_msginfo_new_from_compose(compose);
1173 text = GTK_TEXT_VIEW(compose->text);
1174 buffer = gtk_text_view_get_buffer(text);
1175 gtk_text_buffer_get_start_iter(buffer, &start);
1176 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1177 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1179 compose_quote_fmt(compose, dummyinfo,
1181 NULL, tmp, FALSE, TRUE,
1182 _("The body of the \"New message\" template has an error at line %d."));
1183 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1184 quote_fmt_reset_vartable();
1188 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1189 gtkaspell_highlight_all(compose->gtkaspell);
1191 mfield = BODY_FIELD_PRESENT;
1195 procmsg_msginfo_free( dummyinfo );
1201 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1202 ainfo = (AttachInfo *) curr->data;
1203 compose_attach_append(compose, ainfo->file, ainfo->name,
1204 ainfo->content_type, ainfo->charset);
1208 compose_show_first_last_header(compose, TRUE);
1210 /* Set save folder */
1211 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1212 gchar *folderidentifier;
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1215 folderidentifier = folder_item_get_identifier(item);
1216 compose_set_save_to(compose, folderidentifier);
1217 g_free(folderidentifier);
1220 /* Place cursor according to provided input (mfield) */
1222 case NO_FIELD_PRESENT:
1223 if (compose->header_last)
1224 gtk_widget_grab_focus(compose->header_last->entry);
1226 case TO_FIELD_PRESENT:
1227 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1229 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1232 gtk_widget_grab_focus(compose->subject_entry);
1234 case SUBJECT_FIELD_PRESENT:
1235 textview = GTK_TEXT_VIEW(compose->text);
1238 textbuf = gtk_text_view_get_buffer(textview);
1241 mark = gtk_text_buffer_get_insert(textbuf);
1242 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1243 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1245 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1246 * only defers where it comes to the variable body
1247 * is not null. If no body is present compose->text
1248 * will be null in which case you cannot place the
1249 * cursor inside the component so. An empty component
1250 * is therefore created before placing the cursor
1252 case BODY_FIELD_PRESENT:
1253 cursor_pos = quote_fmt_get_cursor_pos();
1254 if (cursor_pos == -1)
1255 gtk_widget_grab_focus(compose->header_last->entry);
1257 gtk_widget_grab_focus(compose->text);
1261 undo_unblock(compose->undostruct);
1263 if (prefs_common.auto_exteditor)
1264 compose_exec_ext_editor(compose);
1266 compose->draft_timeout_tag = -1;
1267 SCROLL_TO_CURSOR(compose);
1269 compose->modified = FALSE;
1270 compose_set_title(compose);
1272 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1277 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1278 gboolean override_pref, const gchar *system)
1280 const gchar *privacy = NULL;
1282 cm_return_if_fail(compose != NULL);
1283 cm_return_if_fail(account != NULL);
1285 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1288 if (account->default_privacy_system && strlen(account->default_privacy_system))
1289 privacy = account->default_privacy_system;
1293 GSList *privacy_avail = privacy_get_system_ids();
1294 if (privacy_avail && g_slist_length(privacy_avail)) {
1295 privacy = (gchar *)(privacy_avail->data);
1298 if (privacy != NULL) {
1300 g_free(compose->privacy_system);
1301 compose->privacy_system = NULL;
1303 if (compose->privacy_system == NULL)
1304 compose->privacy_system = g_strdup(privacy);
1305 else if (*(compose->privacy_system) == '\0') {
1306 g_free(compose->privacy_system);
1307 compose->privacy_system = g_strdup(privacy);
1309 compose_update_privacy_system_menu_item(compose, FALSE);
1310 compose_use_encryption(compose, TRUE);
1314 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1316 const gchar *privacy = NULL;
1318 if (account->default_privacy_system && strlen(account->default_privacy_system))
1319 privacy = account->default_privacy_system;
1323 GSList *privacy_avail = privacy_get_system_ids();
1324 if (privacy_avail && g_slist_length(privacy_avail)) {
1325 privacy = (gchar *)(privacy_avail->data);
1329 if (privacy != NULL) {
1331 g_free(compose->privacy_system);
1332 compose->privacy_system = NULL;
1334 if (compose->privacy_system == NULL)
1335 compose->privacy_system = g_strdup(privacy);
1336 compose_update_privacy_system_menu_item(compose, FALSE);
1337 compose_use_signing(compose, TRUE);
1341 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1345 Compose *compose = NULL;
1347 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1349 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1350 cm_return_val_if_fail(msginfo != NULL, NULL);
1352 list_len = g_slist_length(msginfo_list);
1356 case COMPOSE_REPLY_TO_ADDRESS:
1357 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1358 FALSE, prefs_common.default_reply_list, FALSE, body);
1360 case COMPOSE_REPLY_WITH_QUOTE:
1361 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1362 FALSE, prefs_common.default_reply_list, FALSE, body);
1364 case COMPOSE_REPLY_WITHOUT_QUOTE:
1365 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1366 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1368 case COMPOSE_REPLY_TO_SENDER:
1369 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1370 FALSE, FALSE, TRUE, body);
1372 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1373 compose = compose_followup_and_reply_to(msginfo,
1374 COMPOSE_QUOTE_CHECK,
1375 FALSE, FALSE, body);
1377 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1379 FALSE, FALSE, TRUE, body);
1381 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1383 FALSE, FALSE, TRUE, NULL);
1385 case COMPOSE_REPLY_TO_ALL:
1386 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1387 TRUE, FALSE, FALSE, body);
1389 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1391 TRUE, FALSE, FALSE, body);
1393 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1394 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1395 TRUE, FALSE, FALSE, NULL);
1397 case COMPOSE_REPLY_TO_LIST:
1398 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1399 FALSE, TRUE, FALSE, body);
1401 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1402 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1403 FALSE, TRUE, FALSE, body);
1405 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1406 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1407 FALSE, TRUE, FALSE, NULL);
1409 case COMPOSE_FORWARD:
1410 if (prefs_common.forward_as_attachment) {
1411 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1414 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1418 case COMPOSE_FORWARD_INLINE:
1419 /* check if we reply to more than one Message */
1420 if (list_len == 1) {
1421 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1424 /* more messages FALL THROUGH */
1425 case COMPOSE_FORWARD_AS_ATTACH:
1426 compose = compose_forward_multiple(NULL, msginfo_list);
1428 case COMPOSE_REDIRECT:
1429 compose = compose_redirect(NULL, msginfo, FALSE);
1432 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1435 if (compose == NULL) {
1436 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1440 compose->rmode = mode;
1441 switch (compose->rmode) {
1443 case COMPOSE_REPLY_WITH_QUOTE:
1444 case COMPOSE_REPLY_WITHOUT_QUOTE:
1445 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1446 debug_print("reply mode Normal\n");
1447 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1448 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1450 case COMPOSE_REPLY_TO_SENDER:
1451 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1452 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1453 debug_print("reply mode Sender\n");
1454 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1456 case COMPOSE_REPLY_TO_ALL:
1457 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1458 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1459 debug_print("reply mode All\n");
1460 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1462 case COMPOSE_REPLY_TO_LIST:
1463 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1464 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1465 debug_print("reply mode List\n");
1466 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1468 case COMPOSE_REPLY_TO_ADDRESS:
1469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1477 static Compose *compose_reply(MsgInfo *msginfo,
1478 ComposeQuoteMode quote_mode,
1484 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1485 to_sender, FALSE, body);
1488 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1489 ComposeQuoteMode quote_mode,
1494 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1495 to_sender, TRUE, body);
1498 static void compose_extract_original_charset(Compose *compose)
1500 MsgInfo *info = NULL;
1501 if (compose->replyinfo) {
1502 info = compose->replyinfo;
1503 } else if (compose->fwdinfo) {
1504 info = compose->fwdinfo;
1505 } else if (compose->targetinfo) {
1506 info = compose->targetinfo;
1509 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1510 MimeInfo *partinfo = mimeinfo;
1511 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1512 partinfo = procmime_mimeinfo_next(partinfo);
1514 compose->orig_charset =
1515 g_strdup(procmime_mimeinfo_get_parameter(
1516 partinfo, "charset"));
1518 procmime_mimeinfo_free_all(mimeinfo);
1522 #define SIGNAL_BLOCK(buffer) { \
1523 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1524 G_CALLBACK(compose_changed_cb), \
1526 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1527 G_CALLBACK(text_inserted), \
1531 #define SIGNAL_UNBLOCK(buffer) { \
1532 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1533 G_CALLBACK(compose_changed_cb), \
1535 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1536 G_CALLBACK(text_inserted), \
1540 static Compose *compose_generic_reply(MsgInfo *msginfo,
1541 ComposeQuoteMode quote_mode,
1542 gboolean to_all, gboolean to_ml,
1544 gboolean followup_and_reply_to,
1548 PrefsAccount *account = NULL;
1549 GtkTextView *textview;
1550 GtkTextBuffer *textbuf;
1551 gboolean quote = FALSE;
1552 const gchar *qmark = NULL;
1553 const gchar *body_fmt = NULL;
1554 gchar *s_system = NULL;
1556 cm_return_val_if_fail(msginfo != NULL, NULL);
1557 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1559 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1561 cm_return_val_if_fail(account != NULL, NULL);
1563 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1565 compose->updating = TRUE;
1567 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1568 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1570 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1571 if (!compose->replyinfo)
1572 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1574 compose_extract_original_charset(compose);
1576 if (msginfo->folder && msginfo->folder->ret_rcpt)
1577 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1579 /* Set save folder */
1580 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1581 gchar *folderidentifier;
1583 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1584 folderidentifier = folder_item_get_identifier(msginfo->folder);
1585 compose_set_save_to(compose, folderidentifier);
1586 g_free(folderidentifier);
1589 if (compose_parse_header(compose, msginfo) < 0) {
1590 compose->updating = FALSE;
1591 compose_destroy(compose);
1595 /* override from name according to folder properties */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format &&
1598 msginfo->folder->prefs->reply_override_from_format &&
1599 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1604 /* decode \-escape sequences in the internal representation of the quote format */
1605 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1606 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1609 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1610 compose->gtkaspell);
1612 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1614 quote_fmt_scan_string(tmp);
1617 buf = quote_fmt_get_buffer();
1619 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1621 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1622 quote_fmt_reset_vartable();
1627 textview = (GTK_TEXT_VIEW(compose->text));
1628 textbuf = gtk_text_view_get_buffer(textview);
1629 compose_create_tags(textview, compose);
1631 undo_block(compose->undostruct);
1633 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1634 gtkaspell_block_check(compose->gtkaspell);
1637 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1638 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1639 /* use the reply format of folder (if enabled), or the account's one
1640 (if enabled) or fallback to the global reply format, which is always
1641 enabled (even if empty), and use the relevant quotemark */
1643 if (msginfo->folder && msginfo->folder->prefs &&
1644 msginfo->folder->prefs->reply_with_format) {
1645 qmark = msginfo->folder->prefs->reply_quotemark;
1646 body_fmt = msginfo->folder->prefs->reply_body_format;
1648 } else if (account->reply_with_format) {
1649 qmark = account->reply_quotemark;
1650 body_fmt = account->reply_body_format;
1653 qmark = prefs_common.quotemark;
1654 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1655 body_fmt = gettext(prefs_common.quotefmt);
1662 /* empty quotemark is not allowed */
1663 if (qmark == NULL || *qmark == '\0')
1665 compose_quote_fmt(compose, compose->replyinfo,
1666 body_fmt, qmark, body, FALSE, TRUE,
1667 _("The body of the \"Reply\" template has an error at line %d."));
1668 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1669 quote_fmt_reset_vartable();
1672 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1673 compose_force_encryption(compose, account, FALSE, s_system);
1676 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1677 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1678 compose_force_signing(compose, account, s_system);
1682 SIGNAL_BLOCK(textbuf);
1684 if (account->auto_sig)
1685 compose_insert_sig(compose, FALSE);
1687 compose_wrap_all(compose);
1690 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1691 gtkaspell_highlight_all(compose->gtkaspell);
1692 gtkaspell_unblock_check(compose->gtkaspell);
1694 SIGNAL_UNBLOCK(textbuf);
1696 gtk_widget_grab_focus(compose->text);
1698 undo_unblock(compose->undostruct);
1700 if (prefs_common.auto_exteditor)
1701 compose_exec_ext_editor(compose);
1703 compose->modified = FALSE;
1704 compose_set_title(compose);
1706 compose->updating = FALSE;
1707 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1708 SCROLL_TO_CURSOR(compose);
1710 if (compose->deferred_destroy) {
1711 compose_destroy(compose);
1719 #define INSERT_FW_HEADER(var, hdr) \
1720 if (msginfo->var && *msginfo->var) { \
1721 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1722 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1723 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1726 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1727 gboolean as_attach, const gchar *body,
1728 gboolean no_extedit,
1732 GtkTextView *textview;
1733 GtkTextBuffer *textbuf;
1734 gint cursor_pos = -1;
1737 cm_return_val_if_fail(msginfo != NULL, NULL);
1738 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1741 !(account = compose_guess_forward_account_from_msginfo
1743 account = cur_account;
1745 if (!prefs_common.forward_as_attachment)
1746 mode = COMPOSE_FORWARD_INLINE;
1748 mode = COMPOSE_FORWARD;
1749 compose = compose_create(account, msginfo->folder, mode, batch);
1751 compose->updating = TRUE;
1752 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1753 if (!compose->fwdinfo)
1754 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1756 compose_extract_original_charset(compose);
1758 if (msginfo->subject && *msginfo->subject) {
1759 gchar *buf, *buf2, *p;
1761 buf = p = g_strdup(msginfo->subject);
1762 p += subject_get_prefix_length(p);
1763 memmove(buf, p, strlen(p) + 1);
1765 buf2 = g_strdup_printf("Fw: %s", buf);
1766 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1772 /* override from name according to folder properties */
1773 if (msginfo->folder && msginfo->folder->prefs &&
1774 msginfo->folder->prefs->forward_with_format &&
1775 msginfo->folder->prefs->forward_override_from_format &&
1776 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1780 MsgInfo *full_msginfo = NULL;
1783 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1785 full_msginfo = procmsg_msginfo_copy(msginfo);
1787 /* decode \-escape sequences in the internal representation of the quote format */
1788 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1789 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1792 gtkaspell_block_check(compose->gtkaspell);
1793 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1794 compose->gtkaspell);
1796 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1798 quote_fmt_scan_string(tmp);
1801 buf = quote_fmt_get_buffer();
1803 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1805 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1806 quote_fmt_reset_vartable();
1809 procmsg_msginfo_free(full_msginfo);
1812 textview = GTK_TEXT_VIEW(compose->text);
1813 textbuf = gtk_text_view_get_buffer(textview);
1814 compose_create_tags(textview, compose);
1816 undo_block(compose->undostruct);
1820 msgfile = procmsg_get_message_file(msginfo);
1821 if (!is_file_exist(msgfile))
1822 g_warning("%s: file not exist\n", msgfile);
1824 compose_attach_append(compose, msgfile, msgfile,
1825 "message/rfc822", NULL);
1829 const gchar *qmark = NULL;
1830 const gchar *body_fmt = NULL;
1831 MsgInfo *full_msginfo;
1833 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1835 full_msginfo = procmsg_msginfo_copy(msginfo);
1837 /* use the forward format of folder (if enabled), or the account's one
1838 (if enabled) or fallback to the global forward format, which is always
1839 enabled (even if empty), and use the relevant quotemark */
1840 if (msginfo->folder && msginfo->folder->prefs &&
1841 msginfo->folder->prefs->forward_with_format) {
1842 qmark = msginfo->folder->prefs->forward_quotemark;
1843 body_fmt = msginfo->folder->prefs->forward_body_format;
1845 } else if (account->forward_with_format) {
1846 qmark = account->forward_quotemark;
1847 body_fmt = account->forward_body_format;
1850 qmark = prefs_common.fw_quotemark;
1851 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1852 body_fmt = gettext(prefs_common.fw_quotefmt);
1857 /* empty quotemark is not allowed */
1858 if (qmark == NULL || *qmark == '\0')
1861 compose_quote_fmt(compose, full_msginfo,
1862 body_fmt, qmark, body, FALSE, TRUE,
1863 _("The body of the \"Forward\" template has an error at line %d."));
1864 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1865 quote_fmt_reset_vartable();
1866 compose_attach_parts(compose, msginfo);
1868 procmsg_msginfo_free(full_msginfo);
1871 SIGNAL_BLOCK(textbuf);
1873 if (account->auto_sig)
1874 compose_insert_sig(compose, FALSE);
1876 compose_wrap_all(compose);
1879 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1880 gtkaspell_highlight_all(compose->gtkaspell);
1881 gtkaspell_unblock_check(compose->gtkaspell);
1883 SIGNAL_UNBLOCK(textbuf);
1885 cursor_pos = quote_fmt_get_cursor_pos();
1886 if (cursor_pos == -1)
1887 gtk_widget_grab_focus(compose->header_last->entry);
1889 gtk_widget_grab_focus(compose->text);
1891 if (!no_extedit && prefs_common.auto_exteditor)
1892 compose_exec_ext_editor(compose);
1895 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1896 gchar *folderidentifier;
1898 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1899 folderidentifier = folder_item_get_identifier(msginfo->folder);
1900 compose_set_save_to(compose, folderidentifier);
1901 g_free(folderidentifier);
1904 undo_unblock(compose->undostruct);
1906 compose->modified = FALSE;
1907 compose_set_title(compose);
1909 compose->updating = FALSE;
1910 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1911 SCROLL_TO_CURSOR(compose);
1913 if (compose->deferred_destroy) {
1914 compose_destroy(compose);
1918 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1923 #undef INSERT_FW_HEADER
1925 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1928 GtkTextView *textview;
1929 GtkTextBuffer *textbuf;
1933 gboolean single_mail = TRUE;
1935 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1937 if (g_slist_length(msginfo_list) > 1)
1938 single_mail = FALSE;
1940 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1941 if (((MsgInfo *)msginfo->data)->folder == NULL)
1944 /* guess account from first selected message */
1946 !(account = compose_guess_forward_account_from_msginfo
1947 (msginfo_list->data)))
1948 account = cur_account;
1950 cm_return_val_if_fail(account != NULL, NULL);
1952 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1953 if (msginfo->data) {
1954 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1955 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1959 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1960 g_warning("no msginfo_list");
1964 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1966 compose->updating = TRUE;
1968 /* override from name according to folder properties */
1969 if (msginfo_list->data) {
1970 MsgInfo *msginfo = msginfo_list->data;
1972 if (msginfo->folder && msginfo->folder->prefs &&
1973 msginfo->folder->prefs->forward_with_format &&
1974 msginfo->folder->prefs->forward_override_from_format &&
1975 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1980 /* decode \-escape sequences in the internal representation of the quote format */
1981 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1982 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1985 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1986 compose->gtkaspell);
1988 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1990 quote_fmt_scan_string(tmp);
1993 buf = quote_fmt_get_buffer();
1995 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1997 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1998 quote_fmt_reset_vartable();
2004 textview = GTK_TEXT_VIEW(compose->text);
2005 textbuf = gtk_text_view_get_buffer(textview);
2006 compose_create_tags(textview, compose);
2008 undo_block(compose->undostruct);
2009 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
2010 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
2012 if (!is_file_exist(msgfile))
2013 g_warning("%s: file not exist\n", msgfile);
2015 compose_attach_append(compose, msgfile, msgfile,
2016 "message/rfc822", NULL);
2021 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2022 if (info->subject && *info->subject) {
2023 gchar *buf, *buf2, *p;
2025 buf = p = g_strdup(info->subject);
2026 p += subject_get_prefix_length(p);
2027 memmove(buf, p, strlen(p) + 1);
2029 buf2 = g_strdup_printf("Fw: %s", buf);
2030 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2036 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2037 _("Fw: multiple emails"));
2040 SIGNAL_BLOCK(textbuf);
2042 if (account->auto_sig)
2043 compose_insert_sig(compose, FALSE);
2045 compose_wrap_all(compose);
2047 SIGNAL_UNBLOCK(textbuf);
2049 gtk_text_buffer_get_start_iter(textbuf, &iter);
2050 gtk_text_buffer_place_cursor(textbuf, &iter);
2052 gtk_widget_grab_focus(compose->header_last->entry);
2053 undo_unblock(compose->undostruct);
2054 compose->modified = FALSE;
2055 compose_set_title(compose);
2057 compose->updating = FALSE;
2058 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2059 SCROLL_TO_CURSOR(compose);
2061 if (compose->deferred_destroy) {
2062 compose_destroy(compose);
2066 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2071 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2073 GtkTextIter start = *iter;
2074 GtkTextIter end_iter;
2075 int start_pos = gtk_text_iter_get_offset(&start);
2077 if (!compose->account->sig_sep)
2080 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2081 start_pos+strlen(compose->account->sig_sep));
2083 /* check sig separator */
2084 str = gtk_text_iter_get_text(&start, &end_iter);
2085 if (!strcmp(str, compose->account->sig_sep)) {
2087 /* check end of line (\n) */
2088 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2089 start_pos+strlen(compose->account->sig_sep));
2090 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2091 start_pos+strlen(compose->account->sig_sep)+1);
2092 tmp = gtk_text_iter_get_text(&start, &end_iter);
2093 if (!strcmp(tmp,"\n")) {
2105 static void compose_colorize_signature(Compose *compose)
2107 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2109 GtkTextIter end_iter;
2110 gtk_text_buffer_get_start_iter(buffer, &iter);
2111 while (gtk_text_iter_forward_line(&iter))
2112 if (compose_is_sig_separator(compose, buffer, &iter)) {
2113 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2114 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2118 #define BLOCK_WRAP() { \
2119 prev_autowrap = compose->autowrap; \
2120 buffer = gtk_text_view_get_buffer( \
2121 GTK_TEXT_VIEW(compose->text)); \
2122 compose->autowrap = FALSE; \
2124 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2125 G_CALLBACK(compose_changed_cb), \
2127 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2128 G_CALLBACK(text_inserted), \
2131 #define UNBLOCK_WRAP() { \
2132 compose->autowrap = prev_autowrap; \
2133 if (compose->autowrap) { \
2134 gint old = compose->draft_timeout_tag; \
2135 compose->draft_timeout_tag = -2; \
2136 compose_wrap_all(compose); \
2137 compose->draft_timeout_tag = old; \
2140 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2141 G_CALLBACK(compose_changed_cb), \
2143 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2144 G_CALLBACK(text_inserted), \
2148 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2150 Compose *compose = NULL;
2151 PrefsAccount *account = NULL;
2152 GtkTextView *textview;
2153 GtkTextBuffer *textbuf;
2157 gchar buf[BUFFSIZE];
2158 gboolean use_signing = FALSE;
2159 gboolean use_encryption = FALSE;
2160 gchar *privacy_system = NULL;
2161 int priority = PRIORITY_NORMAL;
2162 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2163 gboolean autowrap = prefs_common.autowrap;
2164 gboolean autoindent = prefs_common.auto_indent;
2165 HeaderEntry *manual_headers = NULL;
2167 cm_return_val_if_fail(msginfo != NULL, NULL);
2168 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2170 if (compose_put_existing_to_front(msginfo)) {
2174 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2175 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2176 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2177 gchar queueheader_buf[BUFFSIZE];
2180 /* Select Account from queue headers */
2181 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2182 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2183 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2184 account = account_find_from_id(id);
2186 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2187 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2188 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2189 account = account_find_from_id(id);
2191 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2192 sizeof(queueheader_buf), "NAID:")) {
2193 id = atoi(&queueheader_buf[strlen("NAID:")]);
2194 account = account_find_from_id(id);
2196 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2197 sizeof(queueheader_buf), "MAID:")) {
2198 id = atoi(&queueheader_buf[strlen("MAID:")]);
2199 account = account_find_from_id(id);
2201 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2202 sizeof(queueheader_buf), "S:")) {
2203 account = account_find_from_address(queueheader_buf, FALSE);
2205 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2206 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2207 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2208 use_signing = param;
2211 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2212 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2213 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2214 use_signing = param;
2217 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2218 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2219 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2220 use_encryption = param;
2222 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2223 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2224 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2225 use_encryption = param;
2227 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2228 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2229 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2232 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2233 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2234 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2237 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2238 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2239 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2241 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2242 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2243 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2245 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2246 sizeof(queueheader_buf), "X-Priority: ")) {
2247 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2251 sizeof(queueheader_buf), "RMID:")) {
2252 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2253 if (tokens[0] && tokens[1] && tokens[2]) {
2254 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2255 if (orig_item != NULL) {
2256 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2261 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2262 sizeof(queueheader_buf), "FMID:")) {
2263 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2264 if (tokens[0] && tokens[1] && tokens[2]) {
2265 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2266 if (orig_item != NULL) {
2267 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2272 /* Get manual headers */
2273 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2274 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2275 if (*listmh != '\0') {
2276 debug_print("Got manual headers: %s\n", listmh);
2277 manual_headers = procheader_entries_from_str(listmh);
2282 account = msginfo->folder->folder->account;
2285 if (!account && prefs_common.reedit_account_autosel) {
2286 gchar from[BUFFSIZE];
2287 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2288 extract_address(from);
2289 account = account_find_from_address(from, FALSE);
2293 account = cur_account;
2295 cm_return_val_if_fail(account != NULL, NULL);
2297 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2299 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2300 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2301 compose->autowrap = autowrap;
2302 compose->replyinfo = replyinfo;
2303 compose->fwdinfo = fwdinfo;
2305 compose->updating = TRUE;
2306 compose->priority = priority;
2308 if (privacy_system != NULL) {
2309 compose->privacy_system = privacy_system;
2310 compose_use_signing(compose, use_signing);
2311 compose_use_encryption(compose, use_encryption);
2312 compose_update_privacy_system_menu_item(compose, FALSE);
2314 activate_privacy_system(compose, account, FALSE);
2317 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2319 compose_extract_original_charset(compose);
2321 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2322 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2323 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2324 gchar queueheader_buf[BUFFSIZE];
2326 /* Set message save folder */
2327 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2328 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2329 compose_set_save_to(compose, &queueheader_buf[4]);
2331 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2332 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2334 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2339 if (compose_parse_header(compose, msginfo) < 0) {
2340 compose->updating = FALSE;
2341 compose_destroy(compose);
2344 compose_reedit_set_entry(compose, msginfo);
2346 textview = GTK_TEXT_VIEW(compose->text);
2347 textbuf = gtk_text_view_get_buffer(textview);
2348 compose_create_tags(textview, compose);
2350 mark = gtk_text_buffer_get_insert(textbuf);
2351 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2353 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2354 G_CALLBACK(compose_changed_cb),
2357 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2358 fp = procmime_get_first_encrypted_text_content(msginfo);
2360 compose_force_encryption(compose, account, TRUE, NULL);
2363 fp = procmime_get_first_text_content(msginfo);
2366 g_warning("Can't get text part\n");
2370 gboolean prev_autowrap;
2371 GtkTextBuffer *buffer;
2373 while (fgets(buf, sizeof(buf), fp) != NULL) {
2375 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2381 compose_attach_parts(compose, msginfo);
2383 compose_colorize_signature(compose);
2385 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2386 G_CALLBACK(compose_changed_cb),
2389 if (manual_headers != NULL) {
2390 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2391 procheader_entries_free(manual_headers);
2392 compose->updating = FALSE;
2393 compose_destroy(compose);
2396 procheader_entries_free(manual_headers);
2399 gtk_widget_grab_focus(compose->text);
2401 if (prefs_common.auto_exteditor) {
2402 compose_exec_ext_editor(compose);
2404 compose->modified = FALSE;
2405 compose_set_title(compose);
2407 compose->updating = FALSE;
2408 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2409 SCROLL_TO_CURSOR(compose);
2411 if (compose->deferred_destroy) {
2412 compose_destroy(compose);
2416 compose->sig_str = account_get_signature_str(compose->account);
2418 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2423 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2430 cm_return_val_if_fail(msginfo != NULL, NULL);
2433 account = account_get_reply_account(msginfo,
2434 prefs_common.reply_account_autosel);
2435 cm_return_val_if_fail(account != NULL, NULL);
2437 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2439 compose->updating = TRUE;
2441 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2442 compose->replyinfo = NULL;
2443 compose->fwdinfo = NULL;
2445 compose_show_first_last_header(compose, TRUE);
2447 gtk_widget_grab_focus(compose->header_last->entry);
2449 filename = procmsg_get_message_file(msginfo);
2451 if (filename == NULL) {
2452 compose->updating = FALSE;
2453 compose_destroy(compose);
2458 compose->redirect_filename = filename;
2460 /* Set save folder */
2461 item = msginfo->folder;
2462 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2463 gchar *folderidentifier;
2465 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2466 folderidentifier = folder_item_get_identifier(item);
2467 compose_set_save_to(compose, folderidentifier);
2468 g_free(folderidentifier);
2471 compose_attach_parts(compose, msginfo);
2473 if (msginfo->subject)
2474 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2476 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2478 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2479 _("The body of the \"Redirect\" template has an error at line %d."));
2480 quote_fmt_reset_vartable();
2481 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2483 compose_colorize_signature(compose);
2486 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2487 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2488 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2490 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2491 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2492 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2493 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2494 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2495 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2496 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2497 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2499 if (compose->toolbar->draft_btn)
2500 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2501 if (compose->toolbar->insert_btn)
2502 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2503 if (compose->toolbar->attach_btn)
2504 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2505 if (compose->toolbar->sig_btn)
2506 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2507 if (compose->toolbar->exteditor_btn)
2508 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2509 if (compose->toolbar->linewrap_current_btn)
2510 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2511 if (compose->toolbar->linewrap_all_btn)
2512 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2514 compose->modified = FALSE;
2515 compose_set_title(compose);
2516 compose->updating = FALSE;
2517 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2518 SCROLL_TO_CURSOR(compose);
2520 if (compose->deferred_destroy) {
2521 compose_destroy(compose);
2525 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2530 GList *compose_get_compose_list(void)
2532 return compose_list;
2535 void compose_entry_append(Compose *compose, const gchar *address,
2536 ComposeEntryType type, ComposePrefType pref_type)
2538 const gchar *header;
2540 gboolean in_quote = FALSE;
2541 if (!address || *address == '\0') return;
2548 header = N_("Bcc:");
2550 case COMPOSE_REPLYTO:
2551 header = N_("Reply-To:");
2553 case COMPOSE_NEWSGROUPS:
2554 header = N_("Newsgroups:");
2556 case COMPOSE_FOLLOWUPTO:
2557 header = N_( "Followup-To:");
2559 case COMPOSE_INREPLYTO:
2560 header = N_( "In-Reply-To:");
2567 header = prefs_common_translated_header_name(header);
2569 cur = begin = (gchar *)address;
2571 /* we separate the line by commas, but not if we're inside a quoted
2573 while (*cur != '\0') {
2575 in_quote = !in_quote;
2576 if (*cur == ',' && !in_quote) {
2577 gchar *tmp = g_strdup(begin);
2579 tmp[cur-begin]='\0';
2582 while (*tmp == ' ' || *tmp == '\t')
2584 compose_add_header_entry(compose, header, tmp, pref_type);
2591 gchar *tmp = g_strdup(begin);
2593 tmp[cur-begin]='\0';
2594 while (*tmp == ' ' || *tmp == '\t')
2596 compose_add_header_entry(compose, header, tmp, pref_type);
2601 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2603 #if !GTK_CHECK_VERSION(3, 0, 0)
2604 static GdkColor yellow;
2605 static GdkColor black;
2606 static gboolean yellow_initialised = FALSE;
2608 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2609 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2614 #if !GTK_CHECK_VERSION(3, 0, 0)
2615 if (!yellow_initialised) {
2616 gdk_color_parse("#f5f6be", &yellow);
2617 gdk_color_parse("#000000", &black);
2618 yellow_initialised = gdk_colormap_alloc_color(
2619 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2620 yellow_initialised &= gdk_colormap_alloc_color(
2621 gdk_colormap_get_system(), &black, FALSE, TRUE);
2625 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2626 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2627 if (gtk_entry_get_text(entry) &&
2628 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2629 #if !GTK_CHECK_VERSION(3, 0, 0)
2630 if (yellow_initialised) {
2632 gtk_widget_modify_base(
2633 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2634 GTK_STATE_NORMAL, &yellow);
2635 gtk_widget_modify_text(
2636 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2637 GTK_STATE_NORMAL, &black);
2638 #if !GTK_CHECK_VERSION(3, 0, 0)
2645 void compose_toolbar_cb(gint action, gpointer data)
2647 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2648 Compose *compose = (Compose*)toolbar_item->parent;
2650 cm_return_if_fail(compose != NULL);
2654 compose_send_cb(NULL, compose);
2657 compose_send_later_cb(NULL, compose);
2660 compose_draft(compose, COMPOSE_QUIT_EDITING);
2663 compose_insert_file_cb(NULL, compose);
2666 compose_attach_cb(NULL, compose);
2669 compose_insert_sig(compose, FALSE);
2672 compose_ext_editor_cb(NULL, compose);
2674 case A_LINEWRAP_CURRENT:
2675 compose_beautify_paragraph(compose, NULL, TRUE);
2677 case A_LINEWRAP_ALL:
2678 compose_wrap_all_full(compose, TRUE);
2681 compose_address_cb(NULL, compose);
2684 case A_CHECK_SPELLING:
2685 compose_check_all(NULL, compose);
2693 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2698 gchar *subject = NULL;
2702 gchar **attach = NULL;
2703 gchar *inreplyto = NULL;
2704 MailField mfield = NO_FIELD_PRESENT;
2706 /* get mailto parts but skip from */
2707 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2710 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2711 mfield = TO_FIELD_PRESENT;
2714 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2716 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2718 if (!g_utf8_validate (subject, -1, NULL)) {
2719 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2720 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2723 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2725 mfield = SUBJECT_FIELD_PRESENT;
2728 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2729 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2732 gboolean prev_autowrap = compose->autowrap;
2734 compose->autowrap = FALSE;
2736 mark = gtk_text_buffer_get_insert(buffer);
2737 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2739 if (!g_utf8_validate (body, -1, NULL)) {
2740 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2741 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2744 gtk_text_buffer_insert(buffer, &iter, body, -1);
2746 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2748 compose->autowrap = prev_autowrap;
2749 if (compose->autowrap)
2750 compose_wrap_all(compose);
2751 mfield = BODY_FIELD_PRESENT;
2755 gint i = 0, att = 0;
2756 gchar *warn_files = NULL;
2757 while (attach[i] != NULL) {
2758 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2759 if (utf8_filename) {
2760 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2761 gchar *tmp = g_strdup_printf("%s%s\n",
2762 warn_files?warn_files:"",
2768 g_free(utf8_filename);
2770 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2775 alertpanel_notice(ngettext(
2776 "The following file has been attached: \n%s",
2777 "The following files have been attached: \n%s", att), warn_files);
2782 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2795 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2797 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2798 {"Cc:", NULL, TRUE},
2799 {"References:", NULL, FALSE},
2800 {"Bcc:", NULL, TRUE},
2801 {"Newsgroups:", NULL, TRUE},
2802 {"Followup-To:", NULL, TRUE},
2803 {"List-Post:", NULL, FALSE},
2804 {"X-Priority:", NULL, FALSE},
2805 {NULL, NULL, FALSE}};
2821 cm_return_val_if_fail(msginfo != NULL, -1);
2823 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2824 procheader_get_header_fields(fp, hentry);
2827 if (hentry[H_REPLY_TO].body != NULL) {
2828 if (hentry[H_REPLY_TO].body[0] != '\0') {
2830 conv_unmime_header(hentry[H_REPLY_TO].body,
2833 g_free(hentry[H_REPLY_TO].body);
2834 hentry[H_REPLY_TO].body = NULL;
2836 if (hentry[H_CC].body != NULL) {
2837 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2838 g_free(hentry[H_CC].body);
2839 hentry[H_CC].body = NULL;
2841 if (hentry[H_REFERENCES].body != NULL) {
2842 if (compose->mode == COMPOSE_REEDIT)
2843 compose->references = hentry[H_REFERENCES].body;
2845 compose->references = compose_parse_references
2846 (hentry[H_REFERENCES].body, msginfo->msgid);
2847 g_free(hentry[H_REFERENCES].body);
2849 hentry[H_REFERENCES].body = NULL;
2851 if (hentry[H_BCC].body != NULL) {
2852 if (compose->mode == COMPOSE_REEDIT)
2854 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2855 g_free(hentry[H_BCC].body);
2856 hentry[H_BCC].body = NULL;
2858 if (hentry[H_NEWSGROUPS].body != NULL) {
2859 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2860 hentry[H_NEWSGROUPS].body = NULL;
2862 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2863 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2864 compose->followup_to =
2865 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2868 g_free(hentry[H_FOLLOWUP_TO].body);
2869 hentry[H_FOLLOWUP_TO].body = NULL;
2871 if (hentry[H_LIST_POST].body != NULL) {
2872 gchar *to = NULL, *start = NULL;
2874 extract_address(hentry[H_LIST_POST].body);
2875 if (hentry[H_LIST_POST].body[0] != '\0') {
2876 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2878 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2879 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2882 g_free(compose->ml_post);
2883 compose->ml_post = to;
2886 g_free(hentry[H_LIST_POST].body);
2887 hentry[H_LIST_POST].body = NULL;
2890 /* CLAWS - X-Priority */
2891 if (compose->mode == COMPOSE_REEDIT)
2892 if (hentry[H_X_PRIORITY].body != NULL) {
2895 priority = atoi(hentry[H_X_PRIORITY].body);
2896 g_free(hentry[H_X_PRIORITY].body);
2898 hentry[H_X_PRIORITY].body = NULL;
2900 if (priority < PRIORITY_HIGHEST ||
2901 priority > PRIORITY_LOWEST)
2902 priority = PRIORITY_NORMAL;
2904 compose->priority = priority;
2907 if (compose->mode == COMPOSE_REEDIT) {
2908 if (msginfo->inreplyto && *msginfo->inreplyto)
2909 compose->inreplyto = g_strdup(msginfo->inreplyto);
2913 if (msginfo->msgid && *msginfo->msgid)
2914 compose->inreplyto = g_strdup(msginfo->msgid);
2916 if (!compose->references) {
2917 if (msginfo->msgid && *msginfo->msgid) {
2918 if (msginfo->inreplyto && *msginfo->inreplyto)
2919 compose->references =
2920 g_strdup_printf("<%s>\n\t<%s>",
2924 compose->references =
2925 g_strconcat("<", msginfo->msgid, ">",
2927 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2928 compose->references =
2929 g_strconcat("<", msginfo->inreplyto, ">",
2937 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2942 cm_return_val_if_fail(msginfo != NULL, -1);
2944 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2945 procheader_get_header_fields(fp, entries);
2949 while (he != NULL && he->name != NULL) {
2951 GtkListStore *model = NULL;
2953 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2954 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2955 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2956 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2957 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2964 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2966 GSList *ref_id_list, *cur;
2970 ref_id_list = references_list_append(NULL, ref);
2971 if (!ref_id_list) return NULL;
2972 if (msgid && *msgid)
2973 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2978 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2979 /* "<" + Message-ID + ">" + CR+LF+TAB */
2980 len += strlen((gchar *)cur->data) + 5;
2982 if (len > MAX_REFERENCES_LEN) {
2983 /* remove second message-ID */
2984 if (ref_id_list && ref_id_list->next &&
2985 ref_id_list->next->next) {
2986 g_free(ref_id_list->next->data);
2987 ref_id_list = g_slist_remove
2988 (ref_id_list, ref_id_list->next->data);
2990 slist_free_strings_full(ref_id_list);
2997 new_ref = g_string_new("");
2998 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2999 if (new_ref->len > 0)
3000 g_string_append(new_ref, "\n\t");
3001 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
3004 slist_free_strings_full(ref_id_list);
3006 new_ref_str = new_ref->str;
3007 g_string_free(new_ref, FALSE);
3012 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
3013 const gchar *fmt, const gchar *qmark,
3014 const gchar *body, gboolean rewrap,
3015 gboolean need_unescape,
3016 const gchar *err_msg)
3018 MsgInfo* dummyinfo = NULL;
3019 gchar *quote_str = NULL;
3021 gboolean prev_autowrap;
3022 const gchar *trimmed_body = body;
3023 gint cursor_pos = -1;
3024 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3025 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3030 SIGNAL_BLOCK(buffer);
3033 dummyinfo = compose_msginfo_new_from_compose(compose);
3034 msginfo = dummyinfo;
3037 if (qmark != NULL) {
3039 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3040 compose->gtkaspell);
3042 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3044 quote_fmt_scan_string(qmark);
3047 buf = quote_fmt_get_buffer();
3049 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3051 Xstrdup_a(quote_str, buf, goto error)
3054 if (fmt && *fmt != '\0') {
3057 while (*trimmed_body == '\n')
3061 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3062 compose->gtkaspell);
3064 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3066 if (need_unescape) {
3069 /* decode \-escape sequences in the internal representation of the quote format */
3070 tmp = g_malloc(strlen(fmt)+1);
3071 pref_get_unescaped_pref(tmp, fmt);
3072 quote_fmt_scan_string(tmp);
3076 quote_fmt_scan_string(fmt);
3080 buf = quote_fmt_get_buffer();
3082 gint line = quote_fmt_get_line();
3083 alertpanel_error(err_msg, line);
3089 prev_autowrap = compose->autowrap;
3090 compose->autowrap = FALSE;
3092 mark = gtk_text_buffer_get_insert(buffer);
3093 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3094 if (g_utf8_validate(buf, -1, NULL)) {
3095 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3097 gchar *tmpout = NULL;
3098 tmpout = conv_codeset_strdup
3099 (buf, conv_get_locale_charset_str_no_utf8(),
3101 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3103 tmpout = g_malloc(strlen(buf)*2+1);
3104 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3106 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3110 cursor_pos = quote_fmt_get_cursor_pos();
3111 if (cursor_pos == -1)
3112 cursor_pos = gtk_text_iter_get_offset(&iter);
3113 compose->set_cursor_pos = cursor_pos;
3115 gtk_text_buffer_get_start_iter(buffer, &iter);
3116 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3117 gtk_text_buffer_place_cursor(buffer, &iter);
3119 compose->autowrap = prev_autowrap;
3120 if (compose->autowrap && rewrap)
3121 compose_wrap_all(compose);
3128 SIGNAL_UNBLOCK(buffer);
3130 procmsg_msginfo_free( dummyinfo );
3135 /* if ml_post is of type addr@host and from is of type
3136 * addr-anything@host, return TRUE
3138 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3140 gchar *left_ml = NULL;
3141 gchar *right_ml = NULL;
3142 gchar *left_from = NULL;
3143 gchar *right_from = NULL;
3144 gboolean result = FALSE;
3146 if (!ml_post || !from)
3149 left_ml = g_strdup(ml_post);
3150 if (strstr(left_ml, "@")) {
3151 right_ml = strstr(left_ml, "@")+1;
3152 *(strstr(left_ml, "@")) = '\0';
3155 left_from = g_strdup(from);
3156 if (strstr(left_from, "@")) {
3157 right_from = strstr(left_from, "@")+1;
3158 *(strstr(left_from, "@")) = '\0';
3161 if (left_ml && left_from && right_ml && right_from
3162 && !strncmp(left_from, left_ml, strlen(left_ml))
3163 && !strcmp(right_from, right_ml)) {
3172 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3173 gboolean respect_default_to)
3177 if (!folder || !folder->prefs)
3180 if (respect_default_to && folder->prefs->enable_default_to) {
3181 compose_entry_append(compose, folder->prefs->default_to,
3182 COMPOSE_TO, PREF_FOLDER);
3183 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3185 if (folder->prefs->enable_default_cc)
3186 compose_entry_append(compose, folder->prefs->default_cc,
3187 COMPOSE_CC, PREF_FOLDER);
3188 if (folder->prefs->enable_default_bcc)
3189 compose_entry_append(compose, folder->prefs->default_bcc,
3190 COMPOSE_BCC, PREF_FOLDER);
3191 if (folder->prefs->enable_default_replyto)
3192 compose_entry_append(compose, folder->prefs->default_replyto,
3193 COMPOSE_REPLYTO, PREF_FOLDER);
3196 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3201 if (!compose || !msginfo)
3204 if (msginfo->subject && *msginfo->subject) {
3205 buf = p = g_strdup(msginfo->subject);
3206 p += subject_get_prefix_length(p);
3207 memmove(buf, p, strlen(p) + 1);
3209 buf2 = g_strdup_printf("Re: %s", buf);
3210 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3215 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3218 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3219 gboolean to_all, gboolean to_ml,
3221 gboolean followup_and_reply_to)
3223 GSList *cc_list = NULL;
3226 gchar *replyto = NULL;
3227 gchar *ac_email = NULL;
3229 gboolean reply_to_ml = FALSE;
3230 gboolean default_reply_to = FALSE;
3232 cm_return_if_fail(compose->account != NULL);
3233 cm_return_if_fail(msginfo != NULL);
3235 reply_to_ml = to_ml && compose->ml_post;
3237 default_reply_to = msginfo->folder &&
3238 msginfo->folder->prefs->enable_default_reply_to;
3240 if (compose->account->protocol != A_NNTP) {
3241 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3243 if (reply_to_ml && !default_reply_to) {
3245 gboolean is_subscr = is_subscription(compose->ml_post,
3248 /* normal answer to ml post with a reply-to */
3249 compose_entry_append(compose,
3251 COMPOSE_TO, PREF_ML);
3252 if (compose->replyto)
3253 compose_entry_append(compose,
3255 COMPOSE_CC, PREF_ML);
3257 /* answer to subscription confirmation */
3258 if (compose->replyto)
3259 compose_entry_append(compose,
3261 COMPOSE_TO, PREF_ML);
3262 else if (msginfo->from)
3263 compose_entry_append(compose,
3265 COMPOSE_TO, PREF_ML);
3268 else if (!(to_all || to_sender) && default_reply_to) {
3269 compose_entry_append(compose,
3270 msginfo->folder->prefs->default_reply_to,
3271 COMPOSE_TO, PREF_FOLDER);
3272 compose_entry_mark_default_to(compose,
3273 msginfo->folder->prefs->default_reply_to);
3278 Xstrdup_a(tmp1, msginfo->from, return);
3279 extract_address(tmp1);
3280 if (to_all || to_sender ||
3281 !account_find_from_address(tmp1, FALSE))
3282 compose_entry_append(compose,
3283 (compose->replyto && !to_sender)
3284 ? compose->replyto :
3285 msginfo->from ? msginfo->from : "",
3286 COMPOSE_TO, PREF_NONE);
3287 else if (!to_all && !to_sender) {
3288 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3289 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3290 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3291 if (compose->replyto) {
3292 compose_entry_append(compose,
3294 COMPOSE_TO, PREF_NONE);
3296 compose_entry_append(compose,
3297 msginfo->from ? msginfo->from : "",
3298 COMPOSE_TO, PREF_NONE);
3301 /* replying to own mail, use original recp */
3302 compose_entry_append(compose,
3303 msginfo->to ? msginfo->to : "",
3304 COMPOSE_TO, PREF_NONE);
3305 compose_entry_append(compose,
3306 msginfo->cc ? msginfo->cc : "",
3307 COMPOSE_CC, PREF_NONE);
3312 if (to_sender || (compose->followup_to &&
3313 !strncmp(compose->followup_to, "poster", 6)))
3314 compose_entry_append
3316 (compose->replyto ? compose->replyto :
3317 msginfo->from ? msginfo->from : ""),
3318 COMPOSE_TO, PREF_NONE);
3320 else if (followup_and_reply_to || to_all) {
3321 compose_entry_append
3323 (compose->replyto ? compose->replyto :
3324 msginfo->from ? msginfo->from : ""),
3325 COMPOSE_TO, PREF_NONE);
3327 compose_entry_append
3329 compose->followup_to ? compose->followup_to :
3330 compose->newsgroups ? compose->newsgroups : "",
3331 COMPOSE_NEWSGROUPS, PREF_NONE);
3334 compose_entry_append
3336 compose->followup_to ? compose->followup_to :
3337 compose->newsgroups ? compose->newsgroups : "",
3338 COMPOSE_NEWSGROUPS, PREF_NONE);
3340 compose_reply_set_subject(compose, msginfo);
3342 if (to_ml && compose->ml_post) return;
3343 if (!to_all || compose->account->protocol == A_NNTP) return;
3345 if (compose->replyto) {
3346 Xstrdup_a(replyto, compose->replyto, return);
3347 extract_address(replyto);
3349 if (msginfo->from) {
3350 Xstrdup_a(from, msginfo->from, return);
3351 extract_address(from);
3354 if (replyto && from)
3355 cc_list = address_list_append_with_comments(cc_list, from);
3356 if (to_all && msginfo->folder &&
3357 msginfo->folder->prefs->enable_default_reply_to)
3358 cc_list = address_list_append_with_comments(cc_list,
3359 msginfo->folder->prefs->default_reply_to);
3360 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3361 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3363 ac_email = g_utf8_strdown(compose->account->address, -1);
3366 for (cur = cc_list; cur != NULL; cur = cur->next) {
3367 gchar *addr = g_utf8_strdown(cur->data, -1);
3368 extract_address(addr);
3370 if (strcmp(ac_email, addr))
3371 compose_entry_append(compose, (gchar *)cur->data,
3372 COMPOSE_CC, PREF_NONE);
3374 debug_print("Cc address same as compose account's, ignoring\n");
3379 slist_free_strings_full(cc_list);
3385 #define SET_ENTRY(entry, str) \
3388 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3391 #define SET_ADDRESS(type, str) \
3394 compose_entry_append(compose, str, type, PREF_NONE); \
3397 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3399 cm_return_if_fail(msginfo != NULL);
3401 SET_ENTRY(subject_entry, msginfo->subject);
3402 SET_ENTRY(from_name, msginfo->from);
3403 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3404 SET_ADDRESS(COMPOSE_CC, compose->cc);
3405 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3406 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3407 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3408 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3410 compose_update_priority_menu_item(compose);
3411 compose_update_privacy_system_menu_item(compose, FALSE);
3412 compose_show_first_last_header(compose, TRUE);
3418 static void compose_insert_sig(Compose *compose, gboolean replace)
3420 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3421 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3423 GtkTextIter iter, iter_end;
3424 gint cur_pos, ins_pos;
3425 gboolean prev_autowrap;
3426 gboolean found = FALSE;
3427 gboolean exists = FALSE;
3429 cm_return_if_fail(compose->account != NULL);
3433 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3434 G_CALLBACK(compose_changed_cb),
3437 mark = gtk_text_buffer_get_insert(buffer);
3438 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3439 cur_pos = gtk_text_iter_get_offset (&iter);
3442 gtk_text_buffer_get_end_iter(buffer, &iter);
3444 exists = (compose->sig_str != NULL);
3447 GtkTextIter first_iter, start_iter, end_iter;
3449 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3451 if (!exists || compose->sig_str[0] == '\0')
3454 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3455 compose->signature_tag);
3458 /* include previous \n\n */
3459 gtk_text_iter_backward_chars(&first_iter, 1);
3460 start_iter = first_iter;
3461 end_iter = first_iter;
3463 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3464 compose->signature_tag);
3465 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3466 compose->signature_tag);
3468 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3474 g_free(compose->sig_str);
3475 compose->sig_str = account_get_signature_str(compose->account);
3477 cur_pos = gtk_text_iter_get_offset(&iter);
3479 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3480 g_free(compose->sig_str);
3481 compose->sig_str = NULL;
3483 if (compose->sig_inserted == FALSE)
3484 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3485 compose->sig_inserted = TRUE;
3487 cur_pos = gtk_text_iter_get_offset(&iter);
3488 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3490 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3491 gtk_text_iter_forward_chars(&iter, 1);
3492 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3493 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3495 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3496 cur_pos = gtk_text_buffer_get_char_count (buffer);
3499 /* put the cursor where it should be
3500 * either where the quote_fmt says, either where it was */
3501 if (compose->set_cursor_pos < 0)
3502 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3504 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3505 compose->set_cursor_pos);
3507 compose->set_cursor_pos = -1;
3508 gtk_text_buffer_place_cursor(buffer, &iter);
3509 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3510 G_CALLBACK(compose_changed_cb),
3516 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3519 GtkTextBuffer *buffer;
3522 const gchar *cur_encoding;
3523 gchar buf[BUFFSIZE];
3526 gboolean prev_autowrap;
3527 gboolean badtxt = FALSE;
3528 struct stat file_stat;
3530 GString *file_contents = NULL;
3532 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3534 /* get the size of the file we are about to insert */
3535 ret = g_stat(file, &file_stat);
3537 gchar *shortfile = g_path_get_basename(file);
3538 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3540 return COMPOSE_INSERT_NO_FILE;
3541 } else if (prefs_common.warn_large_insert == TRUE) {
3543 /* ask user for confirmation if the file is large */
3544 if (prefs_common.warn_large_insert_size < 0 ||
3545 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3549 msg = g_strdup_printf(_("You are about to insert a file of %s "
3550 "in the message body. Are you sure you want to do that?"),
3551 to_human_readable(file_stat.st_size));
3552 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3553 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3556 /* do we ask for confirmation next time? */
3557 if (aval & G_ALERTDISABLE) {
3558 /* no confirmation next time, disable feature in preferences */
3559 aval &= ~G_ALERTDISABLE;
3560 prefs_common.warn_large_insert = FALSE;
3563 /* abort file insertion if user canceled action */
3564 if (aval != G_ALERTALTERNATE) {
3565 return COMPOSE_INSERT_NO_FILE;
3571 if ((fp = g_fopen(file, "rb")) == NULL) {
3572 FILE_OP_ERROR(file, "fopen");
3573 return COMPOSE_INSERT_READ_ERROR;
3576 prev_autowrap = compose->autowrap;
3577 compose->autowrap = FALSE;
3579 text = GTK_TEXT_VIEW(compose->text);
3580 buffer = gtk_text_view_get_buffer(text);
3581 mark = gtk_text_buffer_get_insert(buffer);
3582 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3584 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3585 G_CALLBACK(text_inserted),
3588 cur_encoding = conv_get_locale_charset_str_no_utf8();
3590 file_contents = g_string_new("");
3591 while (fgets(buf, sizeof(buf), fp) != NULL) {
3594 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3595 str = g_strdup(buf);
3597 str = conv_codeset_strdup
3598 (buf, cur_encoding, CS_INTERNAL);
3601 /* strip <CR> if DOS/Windows file,
3602 replace <CR> with <LF> if Macintosh file. */
3605 if (len > 0 && str[len - 1] != '\n') {
3607 if (str[len] == '\r') str[len] = '\n';
3610 file_contents = g_string_append(file_contents, str);
3614 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3615 g_string_free(file_contents, TRUE);
3617 compose_changed_cb(NULL, compose);
3618 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3619 G_CALLBACK(text_inserted),
3621 compose->autowrap = prev_autowrap;
3622 if (compose->autowrap)
3623 compose_wrap_all(compose);
3628 return COMPOSE_INSERT_INVALID_CHARACTER;
3630 return COMPOSE_INSERT_SUCCESS;
3633 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3634 const gchar *filename,
3635 const gchar *content_type,
3636 const gchar *charset)
3644 GtkListStore *store;
3646 gboolean has_binary = FALSE;
3648 if (!is_file_exist(file)) {
3649 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3650 gboolean result = FALSE;
3651 if (file_from_uri && is_file_exist(file_from_uri)) {
3652 result = compose_attach_append(
3653 compose, file_from_uri,
3654 filename, content_type,
3657 g_free(file_from_uri);
3660 alertpanel_error("File %s doesn't exist\n", filename);
3663 if ((size = get_file_size(file)) < 0) {
3664 alertpanel_error("Can't get file size of %s\n", filename);
3668 alertpanel_error(_("File %s is empty."), filename);
3671 if ((fp = g_fopen(file, "rb")) == NULL) {
3672 alertpanel_error(_("Can't read %s."), filename);
3677 ainfo = g_new0(AttachInfo, 1);
3678 auto_ainfo = g_auto_pointer_new_with_free
3679 (ainfo, (GFreeFunc) compose_attach_info_free);
3680 ainfo->file = g_strdup(file);
3683 ainfo->content_type = g_strdup(content_type);
3684 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3686 MsgFlags flags = {0, 0};
3688 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3689 ainfo->encoding = ENC_7BIT;
3691 ainfo->encoding = ENC_8BIT;
3693 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3694 if (msginfo && msginfo->subject)
3695 name = g_strdup(msginfo->subject);
3697 name = g_path_get_basename(filename ? filename : file);
3699 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3701 procmsg_msginfo_free(msginfo);
3703 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3704 ainfo->charset = g_strdup(charset);
3705 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3707 ainfo->encoding = ENC_BASE64;
3709 name = g_path_get_basename(filename ? filename : file);
3710 ainfo->name = g_strdup(name);
3714 ainfo->content_type = procmime_get_mime_type(file);
3715 if (!ainfo->content_type) {
3716 ainfo->content_type =
3717 g_strdup("application/octet-stream");
3718 ainfo->encoding = ENC_BASE64;
3719 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3721 procmime_get_encoding_for_text_file(file, &has_binary);
3723 ainfo->encoding = ENC_BASE64;
3724 name = g_path_get_basename(filename ? filename : file);
3725 ainfo->name = g_strdup(name);
3729 if (ainfo->name != NULL
3730 && !strcmp(ainfo->name, ".")) {
3731 g_free(ainfo->name);
3735 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3736 g_free(ainfo->content_type);
3737 ainfo->content_type = g_strdup("application/octet-stream");
3738 g_free(ainfo->charset);
3739 ainfo->charset = NULL;
3742 ainfo->size = (goffset)size;
3743 size_text = to_human_readable((goffset)size);
3745 store = GTK_LIST_STORE(gtk_tree_view_get_model
3746 (GTK_TREE_VIEW(compose->attach_clist)));
3748 gtk_list_store_append(store, &iter);
3749 gtk_list_store_set(store, &iter,
3750 COL_MIMETYPE, ainfo->content_type,
3751 COL_SIZE, size_text,
3752 COL_NAME, ainfo->name,
3753 COL_CHARSET, ainfo->charset,
3755 COL_AUTODATA, auto_ainfo,
3758 g_auto_pointer_free(auto_ainfo);
3759 compose_attach_update_label(compose);
3763 static void compose_use_signing(Compose *compose, gboolean use_signing)
3765 compose->use_signing = use_signing;
3766 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3769 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3771 compose->use_encryption = use_encryption;
3772 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3775 #define NEXT_PART_NOT_CHILD(info) \
3777 node = info->node; \
3778 while (node->children) \
3779 node = g_node_last_child(node); \
3780 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3783 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3787 MimeInfo *firsttext = NULL;
3788 MimeInfo *encrypted = NULL;
3791 const gchar *partname = NULL;
3793 mimeinfo = procmime_scan_message(msginfo);
3794 if (!mimeinfo) return;
3796 if (mimeinfo->node->children == NULL) {
3797 procmime_mimeinfo_free_all(mimeinfo);
3801 /* find first content part */
3802 child = (MimeInfo *) mimeinfo->node->children->data;
3803 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3804 child = (MimeInfo *)child->node->children->data;
3807 if (child->type == MIMETYPE_TEXT) {
3809 debug_print("First text part found\n");
3810 } else if (compose->mode == COMPOSE_REEDIT &&
3811 child->type == MIMETYPE_APPLICATION &&
3812 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3813 encrypted = (MimeInfo *)child->node->parent->data;
3816 child = (MimeInfo *) mimeinfo->node->children->data;
3817 while (child != NULL) {
3820 if (child == encrypted) {
3821 /* skip this part of tree */
3822 NEXT_PART_NOT_CHILD(child);
3826 if (child->type == MIMETYPE_MULTIPART) {
3827 /* get the actual content */
3828 child = procmime_mimeinfo_next(child);
3832 if (child == firsttext) {
3833 child = procmime_mimeinfo_next(child);
3837 outfile = procmime_get_tmp_file_name(child);
3838 if ((err = procmime_get_part(outfile, child)) < 0)
3839 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3841 gchar *content_type;
3843 content_type = procmime_get_content_type_str(child->type, child->subtype);
3845 /* if we meet a pgp signature, we don't attach it, but
3846 * we force signing. */
3847 if ((strcmp(content_type, "application/pgp-signature") &&
3848 strcmp(content_type, "application/pkcs7-signature") &&
3849 strcmp(content_type, "application/x-pkcs7-signature"))
3850 || compose->mode == COMPOSE_REDIRECT) {
3851 partname = procmime_mimeinfo_get_parameter(child, "filename");
3852 if (partname == NULL)
3853 partname = procmime_mimeinfo_get_parameter(child, "name");
3854 if (partname == NULL)
3856 compose_attach_append(compose, outfile,
3857 partname, content_type,
3858 procmime_mimeinfo_get_parameter(child, "charset"));
3860 compose_force_signing(compose, compose->account, NULL);
3862 g_free(content_type);
3865 NEXT_PART_NOT_CHILD(child);
3867 procmime_mimeinfo_free_all(mimeinfo);
3870 #undef NEXT_PART_NOT_CHILD
3875 WAIT_FOR_INDENT_CHAR,
3876 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3879 /* return indent length, we allow:
3880 indent characters followed by indent characters or spaces/tabs,
3881 alphabets and numbers immediately followed by indent characters,
3882 and the repeating sequences of the above
3883 If quote ends with multiple spaces, only the first one is included. */
3884 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3885 const GtkTextIter *start, gint *len)
3887 GtkTextIter iter = *start;
3891 IndentState state = WAIT_FOR_INDENT_CHAR;
3894 gint alnum_count = 0;
3895 gint space_count = 0;
3898 if (prefs_common.quote_chars == NULL) {
3902 while (!gtk_text_iter_ends_line(&iter)) {
3903 wc = gtk_text_iter_get_char(&iter);
3904 if (g_unichar_iswide(wc))
3906 clen = g_unichar_to_utf8(wc, ch);
3910 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3911 is_space = g_unichar_isspace(wc);
3913 if (state == WAIT_FOR_INDENT_CHAR) {
3914 if (!is_indent && !g_unichar_isalnum(wc))
3917 quote_len += alnum_count + space_count + 1;
3918 alnum_count = space_count = 0;
3919 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3922 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3923 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3927 else if (is_indent) {
3928 quote_len += alnum_count + space_count + 1;
3929 alnum_count = space_count = 0;
3932 state = WAIT_FOR_INDENT_CHAR;
3936 gtk_text_iter_forward_char(&iter);
3939 if (quote_len > 0 && space_count > 0)
3945 if (quote_len > 0) {
3947 gtk_text_iter_forward_chars(&iter, quote_len);
3948 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3954 /* return >0 if the line is itemized */
3955 static int compose_itemized_length(GtkTextBuffer *buffer,
3956 const GtkTextIter *start)
3958 GtkTextIter iter = *start;
3963 if (gtk_text_iter_ends_line(&iter))
3968 wc = gtk_text_iter_get_char(&iter);
3969 if (!g_unichar_isspace(wc))
3971 gtk_text_iter_forward_char(&iter);
3972 if (gtk_text_iter_ends_line(&iter))
3976 clen = g_unichar_to_utf8(wc, ch);
3980 if (!strchr("*-+", ch[0]))
3983 gtk_text_iter_forward_char(&iter);
3984 if (gtk_text_iter_ends_line(&iter))
3986 wc = gtk_text_iter_get_char(&iter);
3987 if (g_unichar_isspace(wc)) {
3993 /* return the string at the start of the itemization */
3994 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3995 const GtkTextIter *start)
3997 GtkTextIter iter = *start;
4000 GString *item_chars = g_string_new("");
4003 if (gtk_text_iter_ends_line(&iter))
4008 wc = gtk_text_iter_get_char(&iter);
4009 if (!g_unichar_isspace(wc))
4011 gtk_text_iter_forward_char(&iter);
4012 if (gtk_text_iter_ends_line(&iter))
4014 g_string_append_unichar(item_chars, wc);
4017 str = item_chars->str;
4018 g_string_free(item_chars, FALSE);
4022 /* return the number of spaces at a line's start */
4023 static int compose_left_offset_length(GtkTextBuffer *buffer,
4024 const GtkTextIter *start)
4026 GtkTextIter iter = *start;
4029 if (gtk_text_iter_ends_line(&iter))
4033 wc = gtk_text_iter_get_char(&iter);
4034 if (!g_unichar_isspace(wc))
4037 gtk_text_iter_forward_char(&iter);
4038 if (gtk_text_iter_ends_line(&iter))
4042 gtk_text_iter_forward_char(&iter);
4043 if (gtk_text_iter_ends_line(&iter))
4048 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4049 const GtkTextIter *start,
4050 GtkTextIter *break_pos,
4054 GtkTextIter iter = *start, line_end = *start;
4055 PangoLogAttr *attrs;
4062 gboolean can_break = FALSE;
4063 gboolean do_break = FALSE;
4064 gboolean was_white = FALSE;
4065 gboolean prev_dont_break = FALSE;
4067 gtk_text_iter_forward_to_line_end(&line_end);
4068 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4069 len = g_utf8_strlen(str, -1);
4073 g_warning("compose_get_line_break_pos: len = 0!\n");
4077 /* g_print("breaking line: %d: %s (len = %d)\n",
4078 gtk_text_iter_get_line(&iter), str, len); */
4080 attrs = g_new(PangoLogAttr, len + 1);
4082 pango_default_break(str, -1, NULL, attrs, len + 1);
4086 /* skip quote and leading spaces */
4087 for (i = 0; *p != '\0' && i < len; i++) {
4090 wc = g_utf8_get_char(p);
4091 if (i >= quote_len && !g_unichar_isspace(wc))
4093 if (g_unichar_iswide(wc))
4095 else if (*p == '\t')
4099 p = g_utf8_next_char(p);
4102 for (; *p != '\0' && i < len; i++) {
4103 PangoLogAttr *attr = attrs + i;
4107 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4110 was_white = attr->is_white;
4112 /* don't wrap URI */
4113 if ((uri_len = get_uri_len(p)) > 0) {
4115 if (pos > 0 && col > max_col) {
4125 wc = g_utf8_get_char(p);
4126 if (g_unichar_iswide(wc)) {
4128 if (prev_dont_break && can_break && attr->is_line_break)
4130 } else if (*p == '\t')
4134 if (pos > 0 && col > max_col) {
4139 if (*p == '-' || *p == '/')
4140 prev_dont_break = TRUE;
4142 prev_dont_break = FALSE;
4144 p = g_utf8_next_char(p);
4148 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4153 *break_pos = *start;
4154 gtk_text_iter_set_line_offset(break_pos, pos);
4159 static gboolean compose_join_next_line(Compose *compose,
4160 GtkTextBuffer *buffer,
4162 const gchar *quote_str)
4164 GtkTextIter iter_ = *iter, cur, prev, next, end;
4165 PangoLogAttr attrs[3];
4167 gchar *next_quote_str;
4170 gboolean keep_cursor = FALSE;
4172 if (!gtk_text_iter_forward_line(&iter_) ||
4173 gtk_text_iter_ends_line(&iter_)) {
4176 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4178 if ((quote_str || next_quote_str) &&
4179 strcmp2(quote_str, next_quote_str) != 0) {
4180 g_free(next_quote_str);
4183 g_free(next_quote_str);
4186 if (quote_len > 0) {
4187 gtk_text_iter_forward_chars(&end, quote_len);
4188 if (gtk_text_iter_ends_line(&end)) {
4193 /* don't join itemized lines */
4194 if (compose_itemized_length(buffer, &end) > 0) {
4198 /* don't join signature separator */
4199 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4202 /* delete quote str */
4204 gtk_text_buffer_delete(buffer, &iter_, &end);
4206 /* don't join line breaks put by the user */
4208 gtk_text_iter_backward_char(&cur);
4209 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4210 gtk_text_iter_forward_char(&cur);
4214 gtk_text_iter_forward_char(&cur);
4215 /* delete linebreak and extra spaces */
4216 while (gtk_text_iter_backward_char(&cur)) {
4217 wc1 = gtk_text_iter_get_char(&cur);
4218 if (!g_unichar_isspace(wc1))
4223 while (!gtk_text_iter_ends_line(&cur)) {
4224 wc1 = gtk_text_iter_get_char(&cur);
4225 if (!g_unichar_isspace(wc1))
4227 gtk_text_iter_forward_char(&cur);
4230 if (!gtk_text_iter_equal(&prev, &next)) {
4233 mark = gtk_text_buffer_get_insert(buffer);
4234 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4235 if (gtk_text_iter_equal(&prev, &cur))
4237 gtk_text_buffer_delete(buffer, &prev, &next);
4241 /* insert space if required */
4242 gtk_text_iter_backward_char(&prev);
4243 wc1 = gtk_text_iter_get_char(&prev);
4244 wc2 = gtk_text_iter_get_char(&next);
4245 gtk_text_iter_forward_char(&next);
4246 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4247 pango_default_break(str, -1, NULL, attrs, 3);
4248 if (!attrs[1].is_line_break ||
4249 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4250 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4252 gtk_text_iter_backward_char(&iter_);
4253 gtk_text_buffer_place_cursor(buffer, &iter_);
4262 #define ADD_TXT_POS(bp_, ep_, pti_) \
4263 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4264 last = last->next; \
4265 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4266 last->next = NULL; \
4268 g_warning("alloc error scanning URIs\n"); \
4271 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4273 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4274 GtkTextBuffer *buffer;
4275 GtkTextIter iter, break_pos, end_of_line;
4276 gchar *quote_str = NULL;
4278 gboolean wrap_quote = prefs_common.linewrap_quote;
4279 gboolean prev_autowrap = compose->autowrap;
4280 gint startq_offset = -1, noq_offset = -1;
4281 gint uri_start = -1, uri_stop = -1;
4282 gint nouri_start = -1, nouri_stop = -1;
4283 gint num_blocks = 0;
4284 gint quotelevel = -1;
4285 gboolean modified = force;
4286 gboolean removed = FALSE;
4287 gboolean modified_before_remove = FALSE;
4289 gboolean start = TRUE;
4290 gint itemized_len = 0, rem_item_len = 0;
4291 gchar *itemized_chars = NULL;
4292 gboolean item_continuation = FALSE;
4297 if (compose->draft_timeout_tag == -2) {
4301 compose->autowrap = FALSE;
4303 buffer = gtk_text_view_get_buffer(text);
4304 undo_wrapping(compose->undostruct, TRUE);
4309 mark = gtk_text_buffer_get_insert(buffer);
4310 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4314 if (compose->draft_timeout_tag == -2) {
4315 if (gtk_text_iter_ends_line(&iter)) {
4316 while (gtk_text_iter_ends_line(&iter) &&
4317 gtk_text_iter_forward_line(&iter))
4320 while (gtk_text_iter_backward_line(&iter)) {
4321 if (gtk_text_iter_ends_line(&iter)) {
4322 gtk_text_iter_forward_line(&iter);
4328 /* move to line start */
4329 gtk_text_iter_set_line_offset(&iter, 0);
4332 itemized_len = compose_itemized_length(buffer, &iter);
4334 if (!itemized_len) {
4335 itemized_len = compose_left_offset_length(buffer, &iter);
4336 item_continuation = TRUE;
4340 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4342 /* go until paragraph end (empty line) */
4343 while (start || !gtk_text_iter_ends_line(&iter)) {
4344 gchar *scanpos = NULL;
4345 /* parse table - in order of priority */
4347 const gchar *needle; /* token */
4349 /* token search function */
4350 gchar *(*search) (const gchar *haystack,
4351 const gchar *needle);
4352 /* part parsing function */
4353 gboolean (*parse) (const gchar *start,
4354 const gchar *scanpos,
4358 /* part to URI function */
4359 gchar *(*build_uri) (const gchar *bp,
4363 static struct table parser[] = {
4364 {"http://", strcasestr, get_uri_part, make_uri_string},
4365 {"https://", strcasestr, get_uri_part, make_uri_string},
4366 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4367 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4368 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4369 {"www.", strcasestr, get_uri_part, make_http_string},
4370 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4371 {"@", strcasestr, get_email_part, make_email_string}
4373 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4374 gint last_index = PARSE_ELEMS;
4376 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4380 if (!prev_autowrap && num_blocks == 0) {
4382 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4383 G_CALLBACK(text_inserted),
4386 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4389 uri_start = uri_stop = -1;
4391 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4394 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4395 if (startq_offset == -1)
4396 startq_offset = gtk_text_iter_get_offset(&iter);
4397 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4398 if (quotelevel > 2) {
4399 /* recycle colors */
4400 if (prefs_common.recycle_quote_colors)
4409 if (startq_offset == -1)
4410 noq_offset = gtk_text_iter_get_offset(&iter);
4414 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4417 if (gtk_text_iter_ends_line(&iter)) {
4419 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4420 prefs_common.linewrap_len,
4422 GtkTextIter prev, next, cur;
4423 if (prev_autowrap != FALSE || force) {
4424 compose->automatic_break = TRUE;
4426 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4427 compose->automatic_break = FALSE;
4428 if (itemized_len && compose->autoindent) {
4429 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4430 if (!item_continuation)
4431 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4433 } else if (quote_str && wrap_quote) {
4434 compose->automatic_break = TRUE;
4436 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4437 compose->automatic_break = FALSE;
4438 if (itemized_len && compose->autoindent) {
4439 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4440 if (!item_continuation)
4441 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4445 /* remove trailing spaces */
4447 rem_item_len = itemized_len;
4448 while (compose->autoindent && rem_item_len-- > 0)
4449 gtk_text_iter_backward_char(&cur);
4450 gtk_text_iter_backward_char(&cur);
4453 while (!gtk_text_iter_starts_line(&cur)) {
4456 gtk_text_iter_backward_char(&cur);
4457 wc = gtk_text_iter_get_char(&cur);
4458 if (!g_unichar_isspace(wc))
4462 if (!gtk_text_iter_equal(&prev, &next)) {
4463 gtk_text_buffer_delete(buffer, &prev, &next);
4465 gtk_text_iter_forward_char(&break_pos);
4469 gtk_text_buffer_insert(buffer, &break_pos,
4473 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4475 /* move iter to current line start */
4476 gtk_text_iter_set_line_offset(&iter, 0);
4483 /* move iter to next line start */
4489 if (!prev_autowrap && num_blocks > 0) {
4491 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4492 G_CALLBACK(text_inserted),
4496 while (!gtk_text_iter_ends_line(&end_of_line)) {
4497 gtk_text_iter_forward_char(&end_of_line);
4499 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4501 nouri_start = gtk_text_iter_get_offset(&iter);
4502 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4504 walk_pos = gtk_text_iter_get_offset(&iter);
4505 /* FIXME: this looks phony. scanning for anything in the parse table */
4506 for (n = 0; n < PARSE_ELEMS; n++) {
4509 tmp = parser[n].search(walk, parser[n].needle);
4511 if (scanpos == NULL || tmp < scanpos) {
4520 /* check if URI can be parsed */
4521 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4522 (const gchar **)&ep, FALSE)
4523 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4527 strlen(parser[last_index].needle);
4530 uri_start = walk_pos + (bp - o_walk);
4531 uri_stop = walk_pos + (ep - o_walk);
4535 gtk_text_iter_forward_line(&iter);
4538 if (startq_offset != -1) {
4539 GtkTextIter startquote, endquote;
4540 gtk_text_buffer_get_iter_at_offset(
4541 buffer, &startquote, startq_offset);
4544 switch (quotelevel) {
4546 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4547 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4548 gtk_text_buffer_apply_tag_by_name(
4549 buffer, "quote0", &startquote, &endquote);
4550 gtk_text_buffer_remove_tag_by_name(
4551 buffer, "quote1", &startquote, &endquote);
4552 gtk_text_buffer_remove_tag_by_name(
4553 buffer, "quote2", &startquote, &endquote);
4558 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4559 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4560 gtk_text_buffer_apply_tag_by_name(
4561 buffer, "quote1", &startquote, &endquote);
4562 gtk_text_buffer_remove_tag_by_name(
4563 buffer, "quote0", &startquote, &endquote);
4564 gtk_text_buffer_remove_tag_by_name(
4565 buffer, "quote2", &startquote, &endquote);
4570 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4571 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4572 gtk_text_buffer_apply_tag_by_name(
4573 buffer, "quote2", &startquote, &endquote);
4574 gtk_text_buffer_remove_tag_by_name(
4575 buffer, "quote0", &startquote, &endquote);
4576 gtk_text_buffer_remove_tag_by_name(
4577 buffer, "quote1", &startquote, &endquote);
4583 } else if (noq_offset != -1) {
4584 GtkTextIter startnoquote, endnoquote;
4585 gtk_text_buffer_get_iter_at_offset(
4586 buffer, &startnoquote, noq_offset);
4589 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4590 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4591 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4592 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4593 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4594 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4595 gtk_text_buffer_remove_tag_by_name(
4596 buffer, "quote0", &startnoquote, &endnoquote);
4597 gtk_text_buffer_remove_tag_by_name(
4598 buffer, "quote1", &startnoquote, &endnoquote);
4599 gtk_text_buffer_remove_tag_by_name(
4600 buffer, "quote2", &startnoquote, &endnoquote);
4606 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4607 GtkTextIter nouri_start_iter, nouri_end_iter;
4608 gtk_text_buffer_get_iter_at_offset(
4609 buffer, &nouri_start_iter, nouri_start);
4610 gtk_text_buffer_get_iter_at_offset(
4611 buffer, &nouri_end_iter, nouri_stop);
4612 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4613 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4614 gtk_text_buffer_remove_tag_by_name(
4615 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4616 modified_before_remove = modified;
4621 if (uri_start >= 0 && uri_stop > 0) {
4622 GtkTextIter uri_start_iter, uri_end_iter, back;
4623 gtk_text_buffer_get_iter_at_offset(
4624 buffer, &uri_start_iter, uri_start);
4625 gtk_text_buffer_get_iter_at_offset(
4626 buffer, &uri_end_iter, uri_stop);
4627 back = uri_end_iter;
4628 gtk_text_iter_backward_char(&back);
4629 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4630 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4631 gtk_text_buffer_apply_tag_by_name(
4632 buffer, "link", &uri_start_iter, &uri_end_iter);
4634 if (removed && !modified_before_remove) {
4640 // debug_print("not modified, out after %d lines\n", lines);
4644 // debug_print("modified, out after %d lines\n", lines);
4646 g_free(itemized_chars);
4649 undo_wrapping(compose->undostruct, FALSE);
4650 compose->autowrap = prev_autowrap;
4655 void compose_action_cb(void *data)
4657 Compose *compose = (Compose *)data;
4658 compose_wrap_all(compose);
4661 static void compose_wrap_all(Compose *compose)
4663 compose_wrap_all_full(compose, FALSE);
4666 static void compose_wrap_all_full(Compose *compose, gboolean force)
4668 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4669 GtkTextBuffer *buffer;
4671 gboolean modified = TRUE;
4673 buffer = gtk_text_view_get_buffer(text);
4675 gtk_text_buffer_get_start_iter(buffer, &iter);
4676 while (!gtk_text_iter_is_end(&iter) && modified)
4677 modified = compose_beautify_paragraph(compose, &iter, force);
4681 static void compose_set_title(Compose *compose)
4687 edited = compose->modified ? _(" [Edited]") : "";
4689 subject = gtk_editable_get_chars(
4690 GTK_EDITABLE(compose->subject_entry), 0, -1);
4692 #ifndef GENERIC_UMPC
4693 if (subject && strlen(subject))
4694 str = g_strdup_printf(_("%s - Compose message%s"),
4697 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4699 str = g_strdup(_("Compose message"));
4702 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4708 * compose_current_mail_account:
4710 * Find a current mail account (the currently selected account, or the
4711 * default account, if a news account is currently selected). If a
4712 * mail account cannot be found, display an error message.
4714 * Return value: Mail account, or NULL if not found.
4716 static PrefsAccount *
4717 compose_current_mail_account(void)
4721 if (cur_account && cur_account->protocol != A_NNTP)
4724 ac = account_get_default();
4725 if (!ac || ac->protocol == A_NNTP) {
4726 alertpanel_error(_("Account for sending mail is not specified.\n"
4727 "Please select a mail account before sending."));
4734 #define QUOTE_IF_REQUIRED(out, str) \
4736 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4740 len = strlen(str) + 3; \
4741 if ((__tmp = alloca(len)) == NULL) { \
4742 g_warning("can't allocate memory\n"); \
4743 g_string_free(header, TRUE); \
4746 g_snprintf(__tmp, len, "\"%s\"", str); \
4751 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4752 g_warning("can't allocate memory\n"); \
4753 g_string_free(header, TRUE); \
4756 strcpy(__tmp, str); \
4762 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4764 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4768 len = strlen(str) + 3; \
4769 if ((__tmp = alloca(len)) == NULL) { \
4770 g_warning("can't allocate memory\n"); \
4773 g_snprintf(__tmp, len, "\"%s\"", str); \
4778 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4779 g_warning("can't allocate memory\n"); \
4782 strcpy(__tmp, str); \
4788 static void compose_select_account(Compose *compose, PrefsAccount *account,
4791 gchar *from = NULL, *header = NULL;
4792 ComposeHeaderEntry *header_entry;
4793 #if GTK_CHECK_VERSION(2, 24, 0)
4797 cm_return_if_fail(account != NULL);
4799 compose->account = account;
4800 if (account->name && *account->name) {
4802 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4803 from = g_strdup_printf("%s <%s>",
4804 buf, account->address);
4805 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4807 from = g_strdup_printf("<%s>",
4809 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4814 compose_set_title(compose);
4816 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4817 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4819 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4820 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4821 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4823 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4825 activate_privacy_system(compose, account, FALSE);
4827 if (!init && compose->mode != COMPOSE_REDIRECT) {
4828 undo_block(compose->undostruct);
4829 compose_insert_sig(compose, TRUE);
4830 undo_unblock(compose->undostruct);
4833 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4834 #if !GTK_CHECK_VERSION(2, 24, 0)
4835 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4837 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4838 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4839 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4842 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4843 if (account->protocol == A_NNTP) {
4844 if (!strcmp(header, _("To:")))
4845 combobox_select_by_text(
4846 GTK_COMBO_BOX(header_entry->combo),
4849 if (!strcmp(header, _("Newsgroups:")))
4850 combobox_select_by_text(
4851 GTK_COMBO_BOX(header_entry->combo),
4859 /* use account's dict info if set */
4860 if (compose->gtkaspell) {
4861 if (account->enable_default_dictionary)
4862 gtkaspell_change_dict(compose->gtkaspell,
4863 account->default_dictionary, FALSE);
4864 if (account->enable_default_alt_dictionary)
4865 gtkaspell_change_alt_dict(compose->gtkaspell,
4866 account->default_alt_dictionary);
4867 if (account->enable_default_dictionary
4868 || account->enable_default_alt_dictionary)
4869 compose_spell_menu_changed(compose);
4874 gboolean compose_check_for_valid_recipient(Compose *compose) {
4875 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4876 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4877 gboolean recipient_found = FALSE;
4881 /* free to and newsgroup list */
4882 slist_free_strings_full(compose->to_list);
4883 compose->to_list = NULL;
4885 slist_free_strings_full(compose->newsgroup_list);
4886 compose->newsgroup_list = NULL;
4888 /* search header entries for to and newsgroup entries */
4889 for (list = compose->header_list; list; list = list->next) {
4892 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4893 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4896 if (entry[0] != '\0') {
4897 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4898 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4899 compose->to_list = address_list_append(compose->to_list, entry);
4900 recipient_found = TRUE;
4903 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4904 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4905 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4906 recipient_found = TRUE;
4913 return recipient_found;
4916 static gboolean compose_check_for_set_recipients(Compose *compose)
4918 if (compose->account->set_autocc && compose->account->auto_cc) {
4919 gboolean found_other = FALSE;
4921 /* search header entries for to and newsgroup entries */
4922 for (list = compose->header_list; list; list = list->next) {
4925 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4926 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4929 if (strcmp(entry, compose->account->auto_cc)
4930 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4940 if (compose->batch) {
4941 gtk_widget_show_all(compose->window);
4943 aval = alertpanel(_("Send"),
4944 _("The only recipient is the default CC address. Send anyway?"),
4945 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4946 if (aval != G_ALERTALTERNATE)
4950 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4951 gboolean found_other = FALSE;
4953 /* search header entries for to and newsgroup entries */
4954 for (list = compose->header_list; list; list = list->next) {
4957 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4958 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4961 if (strcmp(entry, compose->account->auto_bcc)
4962 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4972 if (compose->batch) {
4973 gtk_widget_show_all(compose->window);
4975 aval = alertpanel(_("Send"),
4976 _("The only recipient is the default BCC address. Send anyway?"),
4977 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4978 if (aval != G_ALERTALTERNATE)
4985 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4989 if (compose_check_for_valid_recipient(compose) == FALSE) {
4990 if (compose->batch) {
4991 gtk_widget_show_all(compose->window);
4993 alertpanel_error(_("Recipient is not specified."));
4997 if (compose_check_for_set_recipients(compose) == FALSE) {
5001 if (!compose->batch && prefs_common.warn_empty_subj == TRUE) {
5002 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5003 if (*str == '\0' && check_everything == TRUE &&
5004 compose->mode != COMPOSE_REDIRECT) {
5006 gchar *button_label;
5009 if (compose->sending)
5010 button_label = _("+_Send");
5012 button_label = _("+_Queue");
5013 message = g_strdup_printf(_("Subject is empty. %s"),
5014 compose->sending?_("Send it anyway?"):
5015 _("Queue it anyway?"));
5017 aval = alertpanel_full(compose->sending?_("Send"):_("Send later"), message,
5018 GTK_STOCK_CANCEL, button_label, NULL, TRUE, NULL,
5019 ALERT_QUESTION, G_ALERTDEFAULT);
5021 if (aval & G_ALERTDISABLE) {
5022 aval &= ~G_ALERTDISABLE;
5023 prefs_common.warn_empty_subj = FALSE;
5025 if (aval != G_ALERTALTERNATE)
5030 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5036 gint compose_send(Compose *compose)
5039 FolderItem *folder = NULL;
5041 gchar *msgpath = NULL;
5042 gboolean discard_window = FALSE;
5043 gchar *errstr = NULL;
5044 gchar *tmsgid = NULL;
5045 MainWindow *mainwin = mainwindow_get_mainwindow();
5046 gboolean queued_removed = FALSE;
5048 if (prefs_common.send_dialog_invisible
5049 || compose->batch == TRUE)
5050 discard_window = TRUE;
5052 compose_allow_user_actions (compose, FALSE);
5053 compose->sending = TRUE;
5055 if (compose_check_entries(compose, TRUE) == FALSE) {
5056 if (compose->batch) {
5057 gtk_widget_show_all(compose->window);
5063 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5066 if (compose->batch) {
5067 gtk_widget_show_all(compose->window);
5070 alertpanel_error(_("Could not queue message for sending:\n\n"
5071 "Charset conversion failed."));
5072 } else if (val == -5) {
5073 alertpanel_error(_("Could not queue message for sending:\n\n"
5074 "Couldn't get recipient encryption key."));
5075 } else if (val == -6) {
5077 } else if (val == -3) {
5078 if (privacy_peek_error())
5079 alertpanel_error(_("Could not queue message for sending:\n\n"
5080 "Signature failed: %s"), privacy_get_error());
5081 } else if (val == -2 && errno != 0) {
5082 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5084 alertpanel_error(_("Could not queue message for sending."));
5089 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5090 if (discard_window) {
5091 compose->sending = FALSE;
5092 compose_close(compose);
5093 /* No more compose access in the normal codepath
5094 * after this point! */
5099 alertpanel_error(_("The message was queued but could not be "
5100 "sent.\nUse \"Send queued messages\" from "
5101 "the main window to retry."));
5102 if (!discard_window) {
5109 if (msgpath == NULL) {
5110 msgpath = folder_item_fetch_msg(folder, msgnum);
5111 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5114 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5115 claws_unlink(msgpath);
5118 if (!discard_window) {
5120 if (!queued_removed)
5121 folder_item_remove_msg(folder, msgnum);
5122 folder_item_scan(folder);
5124 /* make sure we delete that */
5125 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5127 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5128 folder_item_remove_msg(folder, tmp->msgnum);
5129 procmsg_msginfo_free(tmp);
5136 if (!queued_removed)
5137 folder_item_remove_msg(folder, msgnum);
5138 folder_item_scan(folder);
5140 /* make sure we delete that */
5141 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5143 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5144 folder_item_remove_msg(folder, tmp->msgnum);
5145 procmsg_msginfo_free(tmp);
5148 if (!discard_window) {
5149 compose->sending = FALSE;
5150 compose_allow_user_actions (compose, TRUE);
5151 compose_close(compose);
5155 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5156 "the main window to retry."), errstr);
5159 alertpanel_error_log(_("The message was queued but could not be "
5160 "sent.\nUse \"Send queued messages\" from "
5161 "the main window to retry."));
5163 if (!discard_window) {
5172 toolbar_main_set_sensitive(mainwin);
5173 main_window_set_menu_sensitive(mainwin);
5179 compose_allow_user_actions (compose, TRUE);
5180 compose->sending = FALSE;
5181 compose->modified = TRUE;
5182 toolbar_main_set_sensitive(mainwin);
5183 main_window_set_menu_sensitive(mainwin);
5188 static gboolean compose_use_attach(Compose *compose)
5190 GtkTreeModel *model = gtk_tree_view_get_model
5191 (GTK_TREE_VIEW(compose->attach_clist));
5192 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5195 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5198 gchar buf[BUFFSIZE];
5200 gboolean first_to_address;
5201 gboolean first_cc_address;
5203 ComposeHeaderEntry *headerentry;
5204 const gchar *headerentryname;
5205 const gchar *cc_hdr;
5206 const gchar *to_hdr;
5207 gboolean err = FALSE;
5209 debug_print("Writing redirect header\n");
5211 cc_hdr = prefs_common_translated_header_name("Cc:");
5212 to_hdr = prefs_common_translated_header_name("To:");
5214 first_to_address = TRUE;
5215 for (list = compose->header_list; list; list = list->next) {
5216 headerentry = ((ComposeHeaderEntry *)list->data);
5217 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5219 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5220 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5221 Xstrdup_a(str, entstr, return -1);
5223 if (str[0] != '\0') {
5224 compose_convert_header
5225 (compose, buf, sizeof(buf), str,
5226 strlen("Resent-To") + 2, TRUE);
5228 if (first_to_address) {
5229 err |= (fprintf(fp, "Resent-To: ") < 0);
5230 first_to_address = FALSE;
5232 err |= (fprintf(fp, ",") < 0);
5234 err |= (fprintf(fp, "%s", buf) < 0);
5238 if (!first_to_address) {
5239 err |= (fprintf(fp, "\n") < 0);
5242 first_cc_address = TRUE;
5243 for (list = compose->header_list; list; list = list->next) {
5244 headerentry = ((ComposeHeaderEntry *)list->data);
5245 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5247 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5248 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5249 Xstrdup_a(str, strg, return -1);
5251 if (str[0] != '\0') {
5252 compose_convert_header
5253 (compose, buf, sizeof(buf), str,
5254 strlen("Resent-Cc") + 2, TRUE);
5256 if (first_cc_address) {
5257 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5258 first_cc_address = FALSE;
5260 err |= (fprintf(fp, ",") < 0);
5262 err |= (fprintf(fp, "%s", buf) < 0);
5266 if (!first_cc_address) {
5267 err |= (fprintf(fp, "\n") < 0);
5270 return (err ? -1:0);
5273 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5275 gchar buf[BUFFSIZE];
5277 const gchar *entstr;
5278 /* struct utsname utsbuf; */
5279 gboolean err = FALSE;
5281 cm_return_val_if_fail(fp != NULL, -1);
5282 cm_return_val_if_fail(compose->account != NULL, -1);
5283 cm_return_val_if_fail(compose->account->address != NULL, -1);
5286 get_rfc822_date(buf, sizeof(buf));
5287 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5290 if (compose->account->name && *compose->account->name) {
5291 compose_convert_header
5292 (compose, buf, sizeof(buf), compose->account->name,
5293 strlen("From: "), TRUE);
5294 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5295 buf, compose->account->address) < 0);
5297 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5300 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5301 if (*entstr != '\0') {
5302 Xstrdup_a(str, entstr, return -1);
5305 compose_convert_header(compose, buf, sizeof(buf), str,
5306 strlen("Subject: "), FALSE);
5307 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5311 /* Resent-Message-ID */
5312 if (compose->account->set_domain && compose->account->domain) {
5313 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5314 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5315 g_snprintf(buf, sizeof(buf), "%s",
5316 strchr(compose->account->address, '@') ?
5317 strchr(compose->account->address, '@')+1 :
5318 compose->account->address);
5320 g_snprintf(buf, sizeof(buf), "%s", "");
5323 if (compose->account->gen_msgid) {
5325 if (compose->account->msgid_with_addr) {
5326 addr = compose->account->address;
5328 generate_msgid(buf, sizeof(buf), addr);
5329 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5330 compose->msgid = g_strdup(buf);
5332 compose->msgid = NULL;
5335 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5338 /* separator between header and body */
5339 err |= (fputs("\n", fp) == EOF);
5341 return (err ? -1:0);
5344 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5348 gchar buf[BUFFSIZE];
5350 gboolean skip = FALSE;
5351 gboolean err = FALSE;
5352 gchar *not_included[]={
5353 "Return-Path:", "Delivered-To:", "Received:",
5354 "Subject:", "X-UIDL:", "AF:",
5355 "NF:", "PS:", "SRH:",
5356 "SFN:", "DSR:", "MID:",
5357 "CFG:", "PT:", "S:",
5358 "RQ:", "SSV:", "NSV:",
5359 "SSH:", "R:", "MAID:",
5360 "NAID:", "RMID:", "FMID:",
5361 "SCF:", "RRCPT:", "NG:",
5362 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5363 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5364 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5365 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5366 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5369 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5370 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5374 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5376 for (i = 0; not_included[i] != NULL; i++) {
5377 if (g_ascii_strncasecmp(buf, not_included[i],
5378 strlen(not_included[i])) == 0) {
5385 if (fputs(buf, fdest) == -1)
5388 if (!prefs_common.redirect_keep_from) {
5389 if (g_ascii_strncasecmp(buf, "From:",
5390 strlen("From:")) == 0) {
5391 err |= (fputs(" (by way of ", fdest) == EOF);
5392 if (compose->account->name
5393 && *compose->account->name) {
5394 compose_convert_header
5395 (compose, buf, sizeof(buf),
5396 compose->account->name,
5399 err |= (fprintf(fdest, "%s <%s>",
5401 compose->account->address) < 0);
5403 err |= (fprintf(fdest, "%s",
5404 compose->account->address) < 0);
5405 err |= (fputs(")", fdest) == EOF);
5409 if (fputs("\n", fdest) == -1)
5416 if (compose_redirect_write_headers(compose, fdest))
5419 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5420 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5433 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5435 GtkTextBuffer *buffer;
5436 GtkTextIter start, end;
5439 const gchar *out_codeset;
5440 EncodingType encoding = ENC_UNKNOWN;
5441 MimeInfo *mimemsg, *mimetext;
5443 const gchar *src_codeset = CS_INTERNAL;
5444 gchar *from_addr = NULL;
5445 gchar *from_name = NULL;
5447 if (action == COMPOSE_WRITE_FOR_SEND)
5448 attach_parts = TRUE;
5450 /* create message MimeInfo */
5451 mimemsg = procmime_mimeinfo_new();
5452 mimemsg->type = MIMETYPE_MESSAGE;
5453 mimemsg->subtype = g_strdup("rfc822");
5454 mimemsg->content = MIMECONTENT_MEM;
5455 mimemsg->tmp = TRUE; /* must free content later */
5456 mimemsg->data.mem = compose_get_header(compose);
5458 /* Create text part MimeInfo */
5459 /* get all composed text */
5460 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5461 gtk_text_buffer_get_start_iter(buffer, &start);
5462 gtk_text_buffer_get_end_iter(buffer, &end);
5463 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5465 out_codeset = conv_get_charset_str(compose->out_encoding);
5467 if (!out_codeset && is_ascii_str(chars)) {
5468 out_codeset = CS_US_ASCII;
5469 } else if (prefs_common.outgoing_fallback_to_ascii &&
5470 is_ascii_str(chars)) {
5471 out_codeset = CS_US_ASCII;
5472 encoding = ENC_7BIT;
5476 gchar *test_conv_global_out = NULL;
5477 gchar *test_conv_reply = NULL;
5479 /* automatic mode. be automatic. */
5480 codeconv_set_strict(TRUE);
5482 out_codeset = conv_get_outgoing_charset_str();
5484 debug_print("trying to convert to %s\n", out_codeset);
5485 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5488 if (!test_conv_global_out && compose->orig_charset
5489 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5490 out_codeset = compose->orig_charset;
5491 debug_print("failure; trying to convert to %s\n", out_codeset);
5492 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5495 if (!test_conv_global_out && !test_conv_reply) {
5497 out_codeset = CS_INTERNAL;
5498 debug_print("failure; finally using %s\n", out_codeset);
5500 g_free(test_conv_global_out);
5501 g_free(test_conv_reply);
5502 codeconv_set_strict(FALSE);
5505 if (encoding == ENC_UNKNOWN) {
5506 if (prefs_common.encoding_method == CTE_BASE64)
5507 encoding = ENC_BASE64;
5508 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5509 encoding = ENC_QUOTED_PRINTABLE;
5510 else if (prefs_common.encoding_method == CTE_8BIT)
5511 encoding = ENC_8BIT;
5513 encoding = procmime_get_encoding_for_charset(out_codeset);
5516 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5517 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5519 if (action == COMPOSE_WRITE_FOR_SEND) {
5520 codeconv_set_strict(TRUE);
5521 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5522 codeconv_set_strict(FALSE);
5528 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5529 "to the specified %s charset.\n"
5530 "Send it as %s?"), out_codeset, src_codeset);
5531 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5532 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5535 if (aval != G_ALERTALTERNATE) {
5540 out_codeset = src_codeset;
5546 out_codeset = src_codeset;
5551 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5552 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5553 strstr(buf, "\nFrom ") != NULL) {
5554 encoding = ENC_QUOTED_PRINTABLE;
5558 mimetext = procmime_mimeinfo_new();
5559 mimetext->content = MIMECONTENT_MEM;
5560 mimetext->tmp = TRUE; /* must free content later */
5561 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5562 * and free the data, which we need later. */
5563 mimetext->data.mem = g_strdup(buf);
5564 mimetext->type = MIMETYPE_TEXT;
5565 mimetext->subtype = g_strdup("plain");
5566 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5567 g_strdup(out_codeset));
5569 /* protect trailing spaces when signing message */
5570 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5571 privacy_system_can_sign(compose->privacy_system)) {
5572 encoding = ENC_QUOTED_PRINTABLE;
5575 debug_print("main text: %zd bytes encoded as %s in %d\n",
5576 strlen(buf), out_codeset, encoding);
5578 /* check for line length limit */
5579 if (action == COMPOSE_WRITE_FOR_SEND &&
5580 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5581 check_line_length(buf, 1000, &line) < 0) {
5585 msg = g_strdup_printf
5586 (_("Line %d exceeds the line length limit (998 bytes).\n"
5587 "The contents of the message might be broken on the way to the delivery.\n"
5589 "Send it anyway?"), line + 1);
5590 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5592 if (aval != G_ALERTALTERNATE) {
5598 if (encoding != ENC_UNKNOWN)
5599 procmime_encode_content(mimetext, encoding);
5601 /* append attachment parts */
5602 if (compose_use_attach(compose) && attach_parts) {
5603 MimeInfo *mimempart;
5604 gchar *boundary = NULL;
5605 mimempart = procmime_mimeinfo_new();
5606 mimempart->content = MIMECONTENT_EMPTY;
5607 mimempart->type = MIMETYPE_MULTIPART;
5608 mimempart->subtype = g_strdup("mixed");
5612 boundary = generate_mime_boundary(NULL);
5613 } while (strstr(buf, boundary) != NULL);
5615 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5618 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5620 g_node_append(mimempart->node, mimetext->node);
5621 g_node_append(mimemsg->node, mimempart->node);
5623 if (compose_add_attachments(compose, mimempart) < 0)
5626 g_node_append(mimemsg->node, mimetext->node);
5630 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5631 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5632 /* extract name and address */
5633 if (strstr(spec, " <") && strstr(spec, ">")) {
5634 from_addr = g_strdup(strrchr(spec, '<')+1);
5635 *(strrchr(from_addr, '>')) = '\0';
5636 from_name = g_strdup(spec);
5637 *(strrchr(from_name, '<')) = '\0';
5644 /* sign message if sending */
5645 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5646 privacy_system_can_sign(compose->privacy_system))
5647 if (!privacy_sign(compose->privacy_system, mimemsg,
5648 compose->account, from_addr)) {
5655 procmime_write_mimeinfo(mimemsg, fp);
5657 procmime_mimeinfo_free_all(mimemsg);
5662 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5664 GtkTextBuffer *buffer;
5665 GtkTextIter start, end;
5670 if ((fp = g_fopen(file, "wb")) == NULL) {
5671 FILE_OP_ERROR(file, "fopen");
5675 /* chmod for security */
5676 if (change_file_mode_rw(fp, file) < 0) {
5677 FILE_OP_ERROR(file, "chmod");
5678 g_warning("can't change file mode\n");
5681 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5682 gtk_text_buffer_get_start_iter(buffer, &start);
5683 gtk_text_buffer_get_end_iter(buffer, &end);
5684 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5686 chars = conv_codeset_strdup
5687 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5690 if (!chars) return -1;
5693 len = strlen(chars);
5694 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5695 FILE_OP_ERROR(file, "fwrite");
5704 if (fclose(fp) == EOF) {
5705 FILE_OP_ERROR(file, "fclose");
5712 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5715 MsgInfo *msginfo = compose->targetinfo;
5717 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5718 if (!msginfo) return -1;
5720 if (!force && MSG_IS_LOCKED(msginfo->flags))
5723 item = msginfo->folder;
5724 cm_return_val_if_fail(item != NULL, -1);
5726 if (procmsg_msg_exist(msginfo) &&
5727 (folder_has_parent_of_type(item, F_QUEUE) ||
5728 folder_has_parent_of_type(item, F_DRAFT)
5729 || msginfo == compose->autosaved_draft)) {
5730 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5731 g_warning("can't remove the old message\n");
5734 debug_print("removed reedit target %d\n", msginfo->msgnum);
5741 static void compose_remove_draft(Compose *compose)
5744 MsgInfo *msginfo = compose->targetinfo;
5745 drafts = account_get_special_folder(compose->account, F_DRAFT);
5747 if (procmsg_msg_exist(msginfo)) {
5748 folder_item_remove_msg(drafts, msginfo->msgnum);
5753 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5754 gboolean remove_reedit_target)
5756 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5759 static gboolean compose_warn_encryption(Compose *compose)
5761 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5762 AlertValue val = G_ALERTALTERNATE;
5764 if (warning == NULL)
5767 val = alertpanel_full(_("Encryption warning"), warning,
5768 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5769 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5770 if (val & G_ALERTDISABLE) {
5771 val &= ~G_ALERTDISABLE;
5772 if (val == G_ALERTALTERNATE)
5773 privacy_inhibit_encrypt_warning(compose->privacy_system,
5777 if (val == G_ALERTALTERNATE) {
5784 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5785 gchar **msgpath, gboolean check_subject,
5786 gboolean remove_reedit_target)
5793 PrefsAccount *mailac = NULL, *newsac = NULL;
5794 gboolean err = FALSE;
5796 debug_print("queueing message...\n");
5797 cm_return_val_if_fail(compose->account != NULL, -1);
5799 if (compose_check_entries(compose, check_subject) == FALSE) {
5800 if (compose->batch) {
5801 gtk_widget_show_all(compose->window);
5806 if (!compose->to_list && !compose->newsgroup_list) {
5807 g_warning("can't get recipient list.");
5811 if (compose->to_list) {
5812 if (compose->account->protocol != A_NNTP)
5813 mailac = compose->account;
5814 else if (cur_account && cur_account->protocol != A_NNTP)
5815 mailac = cur_account;
5816 else if (!(mailac = compose_current_mail_account())) {
5817 alertpanel_error(_("No account for sending mails available!"));
5822 if (compose->newsgroup_list) {
5823 if (compose->account->protocol == A_NNTP)
5824 newsac = compose->account;
5826 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5831 /* write queue header */
5832 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5833 G_DIR_SEPARATOR, compose, (guint) rand());
5834 debug_print("queuing to %s\n", tmp);
5835 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5836 FILE_OP_ERROR(tmp, "fopen");
5841 if (change_file_mode_rw(fp, tmp) < 0) {
5842 FILE_OP_ERROR(tmp, "chmod");
5843 g_warning("can't change file mode\n");
5846 /* queueing variables */
5847 err |= (fprintf(fp, "AF:\n") < 0);
5848 err |= (fprintf(fp, "NF:0\n") < 0);
5849 err |= (fprintf(fp, "PS:10\n") < 0);
5850 err |= (fprintf(fp, "SRH:1\n") < 0);
5851 err |= (fprintf(fp, "SFN:\n") < 0);
5852 err |= (fprintf(fp, "DSR:\n") < 0);
5854 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5856 err |= (fprintf(fp, "MID:\n") < 0);
5857 err |= (fprintf(fp, "CFG:\n") < 0);
5858 err |= (fprintf(fp, "PT:0\n") < 0);
5859 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5860 err |= (fprintf(fp, "RQ:\n") < 0);
5862 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5864 err |= (fprintf(fp, "SSV:\n") < 0);
5866 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5868 err |= (fprintf(fp, "NSV:\n") < 0);
5869 err |= (fprintf(fp, "SSH:\n") < 0);
5870 /* write recepient list */
5871 if (compose->to_list) {
5872 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5873 for (cur = compose->to_list->next; cur != NULL;
5875 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5876 err |= (fprintf(fp, "\n") < 0);
5878 /* write newsgroup list */
5879 if (compose->newsgroup_list) {
5880 err |= (fprintf(fp, "NG:") < 0);
5881 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5882 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5883 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5884 err |= (fprintf(fp, "\n") < 0);
5886 /* Sylpheed account IDs */
5888 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5890 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5893 if (compose->privacy_system != NULL) {
5894 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5895 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5896 if (compose->use_encryption) {
5898 if (!compose_warn_encryption(compose)) {
5904 if (mailac && mailac->encrypt_to_self) {
5905 GSList *tmp_list = g_slist_copy(compose->to_list);
5906 tmp_list = g_slist_append(tmp_list, compose->account->address);
5907 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5908 g_slist_free(tmp_list);
5910 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5912 if (encdata != NULL) {
5913 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5914 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5915 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5917 } /* else we finally dont want to encrypt */
5919 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5920 /* and if encdata was null, it means there's been a problem in
5923 g_warning("failed to write queue message");
5933 /* Save copy folder */
5934 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5935 gchar *savefolderid;
5937 savefolderid = compose_get_save_to(compose);
5938 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5939 g_free(savefolderid);
5941 /* Save copy folder */
5942 if (compose->return_receipt) {
5943 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5945 /* Message-ID of message replying to */
5946 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5949 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5950 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5953 /* Message-ID of message forwarding to */
5954 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5957 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5958 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5962 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5963 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5965 /* end of headers */
5966 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5968 if (compose->redirect_filename != NULL) {
5969 if (compose_redirect_write_to_file(compose, fp) < 0) {
5977 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5981 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5985 g_warning("failed to write queue message\n");
5991 if (fclose(fp) == EOF) {
5992 FILE_OP_ERROR(tmp, "fclose");
5998 if (item && *item) {
6001 queue = account_get_special_folder(compose->account, F_QUEUE);
6004 g_warning("can't find queue folder\n");
6009 folder_item_scan(queue);
6010 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
6011 g_warning("can't queue the message\n");
6017 if (msgpath == NULL) {
6023 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6024 compose_remove_reedit_target(compose, FALSE);
6027 if ((msgnum != NULL) && (item != NULL)) {
6035 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6038 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6040 struct stat statbuf;
6041 gchar *type, *subtype;
6042 GtkTreeModel *model;
6045 model = gtk_tree_view_get_model(tree_view);
6047 if (!gtk_tree_model_get_iter_first(model, &iter))
6050 gtk_tree_model_get(model, &iter,
6054 if (!is_file_exist(ainfo->file)) {
6055 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6056 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6057 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6059 if (val == G_ALERTDEFAULT) {
6064 mimepart = procmime_mimeinfo_new();
6065 mimepart->content = MIMECONTENT_FILE;
6066 mimepart->data.filename = g_strdup(ainfo->file);
6067 mimepart->tmp = FALSE; /* or we destroy our attachment */
6068 mimepart->offset = 0;
6070 g_stat(ainfo->file, &statbuf);
6071 mimepart->length = statbuf.st_size;
6073 type = g_strdup(ainfo->content_type);
6075 if (!strchr(type, '/')) {
6077 type = g_strdup("application/octet-stream");
6080 subtype = strchr(type, '/') + 1;
6081 *(subtype - 1) = '\0';
6082 mimepart->type = procmime_get_media_type(type);
6083 mimepart->subtype = g_strdup(subtype);
6086 if (mimepart->type == MIMETYPE_MESSAGE &&
6087 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6088 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6089 } else if (mimepart->type == MIMETYPE_TEXT) {
6090 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6091 /* Text parts with no name come from multipart/alternative
6092 * forwards. Make sure the recipient won't look at the
6093 * original HTML part by mistake. */
6094 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6095 ainfo->name = g_strdup_printf(_("Original %s part"),
6099 g_hash_table_insert(mimepart->typeparameters,
6100 g_strdup("charset"), g_strdup(ainfo->charset));
6102 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6103 if (mimepart->type == MIMETYPE_APPLICATION &&
6104 !strcmp2(mimepart->subtype, "octet-stream"))
6105 g_hash_table_insert(mimepart->typeparameters,
6106 g_strdup("name"), g_strdup(ainfo->name));
6107 g_hash_table_insert(mimepart->dispositionparameters,
6108 g_strdup("filename"), g_strdup(ainfo->name));
6109 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6112 if (mimepart->type == MIMETYPE_MESSAGE
6113 || mimepart->type == MIMETYPE_MULTIPART)
6114 ainfo->encoding = ENC_BINARY;
6115 else if (compose->use_signing) {
6116 if (ainfo->encoding == ENC_7BIT)
6117 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6118 else if (ainfo->encoding == ENC_8BIT)
6119 ainfo->encoding = ENC_BASE64;
6124 procmime_encode_content(mimepart, ainfo->encoding);
6126 g_node_append(parent->node, mimepart->node);
6127 } while (gtk_tree_model_iter_next(model, &iter));
6132 #define IS_IN_CUSTOM_HEADER(header) \
6133 (compose->account->add_customhdr && \
6134 custom_header_find(compose->account->customhdr_list, header) != NULL)
6136 static void compose_add_headerfield_from_headerlist(Compose *compose,
6138 const gchar *fieldname,
6139 const gchar *seperator)
6141 gchar *str, *fieldname_w_colon;
6142 gboolean add_field = FALSE;
6144 ComposeHeaderEntry *headerentry;
6145 const gchar *headerentryname;
6146 const gchar *trans_fieldname;
6149 if (IS_IN_CUSTOM_HEADER(fieldname))
6152 debug_print("Adding %s-fields\n", fieldname);
6154 fieldstr = g_string_sized_new(64);
6156 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6157 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6159 for (list = compose->header_list; list; list = list->next) {
6160 headerentry = ((ComposeHeaderEntry *)list->data);
6161 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6163 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6164 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6166 if (str[0] != '\0') {
6168 g_string_append(fieldstr, seperator);
6169 g_string_append(fieldstr, str);
6178 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6179 compose_convert_header
6180 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6181 strlen(fieldname) + 2, TRUE);
6182 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6186 g_free(fieldname_w_colon);
6187 g_string_free(fieldstr, TRUE);
6192 static gchar *compose_get_manual_headers_info(Compose *compose)
6194 GString *sh_header = g_string_new(" ");
6196 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6198 for (list = compose->header_list; list; list = list->next) {
6199 ComposeHeaderEntry *headerentry;
6202 gchar *headername_wcolon;
6203 const gchar *headername_trans;
6205 gboolean standard_header = FALSE;
6207 headerentry = ((ComposeHeaderEntry *)list->data);
6209 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6211 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6216 if (!strstr(tmp, ":")) {
6217 headername_wcolon = g_strconcat(tmp, ":", NULL);
6218 headername = g_strdup(tmp);
6220 headername_wcolon = g_strdup(tmp);
6221 headername = g_strdup(strtok(tmp, ":"));
6225 string = std_headers;
6226 while (*string != NULL) {
6227 headername_trans = prefs_common_translated_header_name(*string);
6228 if (!strcmp(headername_trans, headername_wcolon))
6229 standard_header = TRUE;
6232 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6233 g_string_append_printf(sh_header, "%s ", headername);
6235 g_free(headername_wcolon);
6237 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6238 return g_string_free(sh_header, FALSE);
6241 static gchar *compose_get_header(Compose *compose)
6243 gchar buf[BUFFSIZE];
6244 const gchar *entry_str;
6248 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6250 gchar *from_name = NULL, *from_address = NULL;
6253 cm_return_val_if_fail(compose->account != NULL, NULL);
6254 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6256 header = g_string_sized_new(64);
6259 get_rfc822_date(buf, sizeof(buf));
6260 g_string_append_printf(header, "Date: %s\n", buf);
6264 if (compose->account->name && *compose->account->name) {
6266 QUOTE_IF_REQUIRED(buf, compose->account->name);
6267 tmp = g_strdup_printf("%s <%s>",
6268 buf, compose->account->address);
6270 tmp = g_strdup_printf("%s",
6271 compose->account->address);
6273 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6274 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6276 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6277 from_address = g_strdup(compose->account->address);
6279 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6280 /* extract name and address */
6281 if (strstr(spec, " <") && strstr(spec, ">")) {
6282 from_address = g_strdup(strrchr(spec, '<')+1);
6283 *(strrchr(from_address, '>')) = '\0';
6284 from_name = g_strdup(spec);
6285 *(strrchr(from_name, '<')) = '\0';
6288 from_address = g_strdup(spec);
6295 if (from_name && *from_name) {
6296 compose_convert_header
6297 (compose, buf, sizeof(buf), from_name,
6298 strlen("From: "), TRUE);
6299 QUOTE_IF_REQUIRED(name, buf);
6301 g_string_append_printf(header, "From: %s <%s>\n",
6302 name, from_address);
6304 g_string_append_printf(header, "From: %s\n", from_address);
6307 g_free(from_address);
6310 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6313 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6316 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6320 * If this account is a NNTP account remove Bcc header from
6321 * message body since it otherwise will be publicly shown
6323 if (compose->account->protocol != A_NNTP)
6324 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6327 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6329 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6332 compose_convert_header(compose, buf, sizeof(buf), str,
6333 strlen("Subject: "), FALSE);
6334 g_string_append_printf(header, "Subject: %s\n", buf);
6340 if (compose->account->set_domain && compose->account->domain) {
6341 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6342 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6343 g_snprintf(buf, sizeof(buf), "%s",
6344 strchr(compose->account->address, '@') ?
6345 strchr(compose->account->address, '@')+1 :
6346 compose->account->address);
6348 g_snprintf(buf, sizeof(buf), "%s", "");
6351 if (compose->account->gen_msgid) {
6353 if (compose->account->msgid_with_addr) {
6354 addr = compose->account->address;
6356 generate_msgid(buf, sizeof(buf), addr);
6357 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6358 compose->msgid = g_strdup(buf);
6360 compose->msgid = NULL;
6363 if (compose->remove_references == FALSE) {
6365 if (compose->inreplyto && compose->to_list)
6366 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6369 if (compose->references)
6370 g_string_append_printf(header, "References: %s\n", compose->references);
6374 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6377 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6380 if (compose->account->organization &&
6381 strlen(compose->account->organization) &&
6382 !IS_IN_CUSTOM_HEADER("Organization")) {
6383 compose_convert_header(compose, buf, sizeof(buf),
6384 compose->account->organization,
6385 strlen("Organization: "), FALSE);
6386 g_string_append_printf(header, "Organization: %s\n", buf);
6389 /* Program version and system info */
6390 if (compose->account->gen_xmailer &&
6391 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6392 !compose->newsgroup_list) {
6393 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6395 gtk_major_version, gtk_minor_version, gtk_micro_version,
6398 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6399 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6401 gtk_major_version, gtk_minor_version, gtk_micro_version,
6405 /* custom headers */
6406 if (compose->account->add_customhdr) {
6409 for (cur = compose->account->customhdr_list; cur != NULL;
6411 CustomHeader *chdr = (CustomHeader *)cur->data;
6413 if (custom_header_is_allowed(chdr->name)
6414 && chdr->value != NULL
6415 && *(chdr->value) != '\0') {
6416 compose_convert_header
6417 (compose, buf, sizeof(buf),
6419 strlen(chdr->name) + 2, FALSE);
6420 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6425 /* Automatic Faces and X-Faces */
6426 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6427 g_string_append_printf(header, "X-Face: %s\n", buf);
6429 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6430 g_string_append_printf(header, "X-Face: %s\n", buf);
6432 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6433 g_string_append_printf(header, "Face: %s\n", buf);
6435 else if (get_default_face (buf, sizeof(buf)) == 0) {
6436 g_string_append_printf(header, "Face: %s\n", buf);
6440 switch (compose->priority) {
6441 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6442 "X-Priority: 1 (Highest)\n");
6444 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6445 "X-Priority: 2 (High)\n");
6447 case PRIORITY_NORMAL: break;
6448 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6449 "X-Priority: 4 (Low)\n");
6451 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6452 "X-Priority: 5 (Lowest)\n");
6454 default: debug_print("compose: priority unknown : %d\n",
6458 /* Request Return Receipt */
6459 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6460 if (compose->return_receipt) {
6461 if (compose->account->name
6462 && *compose->account->name) {
6463 compose_convert_header(compose, buf, sizeof(buf),
6464 compose->account->name,
6465 strlen("Disposition-Notification-To: "),
6467 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6469 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6473 /* get special headers */
6474 for (list = compose->header_list; list; list = list->next) {
6475 ComposeHeaderEntry *headerentry;
6478 gchar *headername_wcolon;
6479 const gchar *headername_trans;
6482 gboolean standard_header = FALSE;
6484 headerentry = ((ComposeHeaderEntry *)list->data);
6486 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6488 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6493 if (!strstr(tmp, ":")) {
6494 headername_wcolon = g_strconcat(tmp, ":", NULL);
6495 headername = g_strdup(tmp);
6497 headername_wcolon = g_strdup(tmp);
6498 headername = g_strdup(strtok(tmp, ":"));
6502 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6503 Xstrdup_a(headervalue, entry_str, return NULL);
6504 subst_char(headervalue, '\r', ' ');
6505 subst_char(headervalue, '\n', ' ');
6506 string = std_headers;
6507 while (*string != NULL) {
6508 headername_trans = prefs_common_translated_header_name(*string);
6509 if (!strcmp(headername_trans, headername_wcolon))
6510 standard_header = TRUE;
6513 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6514 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6517 g_free(headername_wcolon);
6521 g_string_free(header, FALSE);
6526 #undef IS_IN_CUSTOM_HEADER
6528 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6529 gint header_len, gboolean addr_field)
6531 gchar *tmpstr = NULL;
6532 const gchar *out_codeset = NULL;
6534 cm_return_if_fail(src != NULL);
6535 cm_return_if_fail(dest != NULL);
6537 if (len < 1) return;
6539 tmpstr = g_strdup(src);
6541 subst_char(tmpstr, '\n', ' ');
6542 subst_char(tmpstr, '\r', ' ');
6545 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6546 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6547 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6552 codeconv_set_strict(TRUE);
6553 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6554 conv_get_charset_str(compose->out_encoding));
6555 codeconv_set_strict(FALSE);
6557 if (!dest || *dest == '\0') {
6558 gchar *test_conv_global_out = NULL;
6559 gchar *test_conv_reply = NULL;
6561 /* automatic mode. be automatic. */
6562 codeconv_set_strict(TRUE);
6564 out_codeset = conv_get_outgoing_charset_str();
6566 debug_print("trying to convert to %s\n", out_codeset);
6567 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6570 if (!test_conv_global_out && compose->orig_charset
6571 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6572 out_codeset = compose->orig_charset;
6573 debug_print("failure; trying to convert to %s\n", out_codeset);
6574 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6577 if (!test_conv_global_out && !test_conv_reply) {
6579 out_codeset = CS_INTERNAL;
6580 debug_print("finally using %s\n", out_codeset);
6582 g_free(test_conv_global_out);
6583 g_free(test_conv_reply);
6584 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6586 codeconv_set_strict(FALSE);
6591 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6595 cm_return_if_fail(user_data != NULL);
6597 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6598 g_strstrip(address);
6599 if (*address != '\0') {
6600 gchar *name = procheader_get_fromname(address);
6601 extract_address(address);
6602 #ifndef USE_NEW_ADDRBOOK
6603 addressbook_add_contact(name, address, NULL, NULL);
6605 debug_print("%s: %s\n", name, address);
6606 if (addressadd_selection(name, address, NULL, NULL)) {
6607 debug_print( "addressbook_add_contact - added\n" );
6614 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6616 GtkWidget *menuitem;
6619 cm_return_if_fail(menu != NULL);
6620 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6622 menuitem = gtk_separator_menu_item_new();
6623 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6624 gtk_widget_show(menuitem);
6626 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6627 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6629 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6630 g_strstrip(address);
6631 if (*address == '\0') {
6632 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6635 g_signal_connect(G_OBJECT(menuitem), "activate",
6636 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6637 gtk_widget_show(menuitem);
6640 void compose_add_extra_header(gchar *header, GtkListStore *model)
6643 if (strcmp(header, "")) {
6644 COMBOBOX_ADD(model, header, COMPOSE_TO);
6648 void compose_add_extra_header_entries(GtkListStore *model)
6652 gchar buf[BUFFSIZE];
6655 if (extra_headers == NULL) {
6656 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6657 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6658 debug_print("extra headers file not found\n");
6659 goto extra_headers_done;
6661 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6662 lastc = strlen(buf) - 1; /* remove trailing control chars */
6663 while (lastc >= 0 && buf[lastc] != ':')
6664 buf[lastc--] = '\0';
6665 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6666 buf[lastc] = '\0'; /* remove trailing : for comparison */
6667 if (custom_header_is_allowed(buf)) {
6669 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6672 g_message("disallowed extra header line: %s\n", buf);
6676 g_message("invalid extra header line: %s\n", buf);
6682 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6683 extra_headers = g_slist_reverse(extra_headers);
6685 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6688 static void compose_create_header_entry(Compose *compose)
6690 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6697 const gchar *header = NULL;
6698 ComposeHeaderEntry *headerentry;
6699 gboolean standard_header = FALSE;
6700 GtkListStore *model;
6702 #if !(GTK_CHECK_VERSION(2,12,0))
6703 GtkTooltips *tips = compose->tooltips;
6706 headerentry = g_new0(ComposeHeaderEntry, 1);
6708 /* Combo box model */
6709 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6710 #if !GTK_CHECK_VERSION(2, 24, 0)
6711 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6713 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6715 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6717 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6719 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6720 COMPOSE_NEWSGROUPS);
6721 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6723 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6724 COMPOSE_FOLLOWUPTO);
6725 compose_add_extra_header_entries(model);
6728 #if GTK_CHECK_VERSION(2, 24, 0)
6729 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6730 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6731 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6732 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6733 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo), 0);
6735 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6736 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6737 G_CALLBACK(compose_grab_focus_cb), compose);
6738 gtk_widget_show(combo);
6741 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6742 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6745 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6746 compose->header_nextrow, compose->header_nextrow+1,
6747 GTK_SHRINK, GTK_FILL, 0, 0);
6748 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6749 const gchar *last_header_entry = gtk_entry_get_text(
6750 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6752 while (*string != NULL) {
6753 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6754 standard_header = TRUE;
6757 if (standard_header)
6758 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6760 if (!compose->header_last || !standard_header) {
6761 switch(compose->account->protocol) {
6763 header = prefs_common_translated_header_name("Newsgroups:");
6766 header = prefs_common_translated_header_name("To:");
6771 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6773 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6774 G_CALLBACK(compose_grab_focus_cb), compose);
6776 /* Entry field with cleanup button */
6777 button = gtk_button_new();
6778 gtk_button_set_image(GTK_BUTTON(button),
6779 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6780 gtk_widget_show(button);
6781 CLAWS_SET_TIP(button,
6782 _("Delete entry contents"));
6783 entry = gtk_entry_new();
6784 gtk_widget_show(entry);
6785 CLAWS_SET_TIP(entry,
6786 _("Use <tab> to autocomplete from addressbook"));
6787 hbox = gtk_hbox_new (FALSE, 0);
6788 gtk_widget_show(hbox);
6789 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6790 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6791 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6792 compose->header_nextrow, compose->header_nextrow+1,
6793 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6795 g_signal_connect(G_OBJECT(entry), "key-press-event",
6796 G_CALLBACK(compose_headerentry_key_press_event_cb),
6798 g_signal_connect(G_OBJECT(entry), "changed",
6799 G_CALLBACK(compose_headerentry_changed_cb),
6801 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6802 G_CALLBACK(compose_grab_focus_cb), compose);
6804 g_signal_connect(G_OBJECT(button), "clicked",
6805 G_CALLBACK(compose_headerentry_button_clicked_cb),
6809 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6810 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6811 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6812 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6813 G_CALLBACK(compose_header_drag_received_cb),
6815 g_signal_connect(G_OBJECT(entry), "drag-drop",
6816 G_CALLBACK(compose_drag_drop),
6818 g_signal_connect(G_OBJECT(entry), "populate-popup",
6819 G_CALLBACK(compose_entry_popup_extend),
6822 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6824 headerentry->compose = compose;
6825 headerentry->combo = combo;
6826 headerentry->entry = entry;
6827 headerentry->button = button;
6828 headerentry->hbox = hbox;
6829 headerentry->headernum = compose->header_nextrow;
6830 headerentry->type = PREF_NONE;
6832 compose->header_nextrow++;
6833 compose->header_last = headerentry;
6834 compose->header_list =
6835 g_slist_append(compose->header_list,
6839 static void compose_add_header_entry(Compose *compose, const gchar *header,
6840 gchar *text, ComposePrefType pref_type)
6842 ComposeHeaderEntry *last_header = compose->header_last;
6843 gchar *tmp = g_strdup(text), *email;
6844 gboolean replyto_hdr;
6846 replyto_hdr = (!strcasecmp(header,
6847 prefs_common_translated_header_name("Reply-To:")) ||
6849 prefs_common_translated_header_name("Followup-To:")) ||
6851 prefs_common_translated_header_name("In-Reply-To:")));
6853 extract_address(tmp);
6854 email = g_utf8_strdown(tmp, -1);
6856 if (replyto_hdr == FALSE &&
6857 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6859 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6860 header, text, (gint) pref_type);
6866 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6867 gtk_entry_set_text(GTK_ENTRY(
6868 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6870 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6871 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6872 last_header->type = pref_type;
6874 if (replyto_hdr == FALSE)
6875 g_hash_table_insert(compose->email_hashtable, email,
6876 GUINT_TO_POINTER(1));
6883 static void compose_destroy_headerentry(Compose *compose,
6884 ComposeHeaderEntry *headerentry)
6886 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6889 extract_address(text);
6890 email = g_utf8_strdown(text, -1);
6891 g_hash_table_remove(compose->email_hashtable, email);
6895 gtk_widget_destroy(headerentry->combo);
6896 gtk_widget_destroy(headerentry->entry);
6897 gtk_widget_destroy(headerentry->button);
6898 gtk_widget_destroy(headerentry->hbox);
6899 g_free(headerentry);
6902 static void compose_remove_header_entries(Compose *compose)
6905 for (list = compose->header_list; list; list = list->next)
6906 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6908 compose->header_last = NULL;
6909 g_slist_free(compose->header_list);
6910 compose->header_list = NULL;
6911 compose->header_nextrow = 1;
6912 compose_create_header_entry(compose);
6915 static GtkWidget *compose_create_header(Compose *compose)
6917 GtkWidget *from_optmenu_hbox;
6918 GtkWidget *header_scrolledwin_main;
6919 GtkWidget *header_table_main;
6920 GtkWidget *header_scrolledwin;
6921 GtkWidget *header_table;
6923 /* parent with account selection and from header */
6924 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6925 gtk_widget_show(header_scrolledwin_main);
6926 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6928 header_table_main = gtk_table_new(2, 2, FALSE);
6929 gtk_widget_show(header_table_main);
6930 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6931 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6932 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6934 from_optmenu_hbox = compose_account_option_menu_create(compose);
6935 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6936 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6938 /* child with header labels and entries */
6939 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6940 gtk_widget_show(header_scrolledwin);
6941 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6943 header_table = gtk_table_new(2, 2, FALSE);
6944 gtk_widget_show(header_table);
6945 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6946 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6947 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6949 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6950 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6952 compose->header_table = header_table;
6953 compose->header_list = NULL;
6954 compose->header_nextrow = 0;
6956 compose_create_header_entry(compose);
6958 compose->table = NULL;
6960 return header_scrolledwin_main;
6963 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6965 Compose *compose = (Compose *)data;
6966 GdkEventButton event;
6969 event.time = gtk_get_current_event_time();
6971 return attach_button_pressed(compose->attach_clist, &event, compose);
6974 static GtkWidget *compose_create_attach(Compose *compose)
6976 GtkWidget *attach_scrwin;
6977 GtkWidget *attach_clist;
6979 GtkListStore *store;
6980 GtkCellRenderer *renderer;
6981 GtkTreeViewColumn *column;
6982 GtkTreeSelection *selection;
6984 /* attachment list */
6985 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6986 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6987 GTK_POLICY_AUTOMATIC,
6988 GTK_POLICY_AUTOMATIC);
6989 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6991 store = gtk_list_store_new(N_ATTACH_COLS,
6997 G_TYPE_AUTO_POINTER,
6999 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
7000 (GTK_TREE_MODEL(store)));
7001 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
7002 g_object_unref(store);
7004 renderer = gtk_cell_renderer_text_new();
7005 column = gtk_tree_view_column_new_with_attributes
7006 (_("Mime type"), renderer, "text",
7007 COL_MIMETYPE, NULL);
7008 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7010 renderer = gtk_cell_renderer_text_new();
7011 column = gtk_tree_view_column_new_with_attributes
7012 (_("Size"), renderer, "text",
7014 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7016 renderer = gtk_cell_renderer_text_new();
7017 column = gtk_tree_view_column_new_with_attributes
7018 (_("Name"), renderer, "text",
7020 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7022 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7023 prefs_common.use_stripes_everywhere);
7024 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7025 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7027 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7028 G_CALLBACK(attach_selected), compose);
7029 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7030 G_CALLBACK(attach_button_pressed), compose);
7031 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7032 G_CALLBACK(popup_attach_button_pressed), compose);
7033 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7034 G_CALLBACK(attach_key_pressed), compose);
7037 gtk_drag_dest_set(attach_clist,
7038 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7039 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7040 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7041 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7042 G_CALLBACK(compose_attach_drag_received_cb),
7044 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7045 G_CALLBACK(compose_drag_drop),
7048 compose->attach_scrwin = attach_scrwin;
7049 compose->attach_clist = attach_clist;
7051 return attach_scrwin;
7054 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7055 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7057 static GtkWidget *compose_create_others(Compose *compose)
7060 GtkWidget *savemsg_checkbtn;
7061 GtkWidget *savemsg_combo;
7062 GtkWidget *savemsg_select;
7065 gchar *folderidentifier;
7067 /* Table for settings */
7068 table = gtk_table_new(3, 1, FALSE);
7069 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7070 gtk_widget_show(table);
7071 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7074 /* Save Message to folder */
7075 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7076 gtk_widget_show(savemsg_checkbtn);
7077 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7078 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7079 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7081 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7082 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7084 #if !GTK_CHECK_VERSION(2, 24, 0)
7085 savemsg_combo = gtk_combo_box_entry_new_text();
7087 savemsg_combo = gtk_combo_box_text_new_with_entry();
7089 compose->savemsg_checkbtn = savemsg_checkbtn;
7090 compose->savemsg_combo = savemsg_combo;
7091 gtk_widget_show(savemsg_combo);
7093 if (prefs_common.compose_save_to_history)
7094 #if !GTK_CHECK_VERSION(2, 24, 0)
7095 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7096 prefs_common.compose_save_to_history);
7098 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo),
7099 prefs_common.compose_save_to_history);
7101 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7102 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7103 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7104 G_CALLBACK(compose_grab_focus_cb), compose);
7105 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7106 folderidentifier = folder_item_get_identifier(account_get_special_folder
7107 (compose->account, F_OUTBOX));
7108 compose_set_save_to(compose, folderidentifier);
7109 g_free(folderidentifier);
7112 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7113 gtk_widget_show(savemsg_select);
7114 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7115 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7116 G_CALLBACK(compose_savemsg_select_cb),
7122 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7124 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7125 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7128 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7133 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7136 path = folder_item_get_identifier(dest);
7138 compose_set_save_to(compose, path);
7142 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7143 GdkAtom clip, GtkTextIter *insert_place);
7146 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7150 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7152 if (event->button == 3) {
7154 GtkTextIter sel_start, sel_end;
7155 gboolean stuff_selected;
7157 /* move the cursor to allow GtkAspell to check the word
7158 * under the mouse */
7159 if (event->x && event->y) {
7160 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7161 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7163 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7166 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7167 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7170 stuff_selected = gtk_text_buffer_get_selection_bounds(
7172 &sel_start, &sel_end);
7174 gtk_text_buffer_place_cursor (buffer, &iter);
7175 /* reselect stuff */
7177 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7178 gtk_text_buffer_select_range(buffer,
7179 &sel_start, &sel_end);
7181 return FALSE; /* pass the event so that the right-click goes through */
7184 if (event->button == 2) {
7189 /* get the middle-click position to paste at the correct place */
7190 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7191 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7193 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7196 entry_paste_clipboard(compose, text,
7197 prefs_common.linewrap_pastes,
7198 GDK_SELECTION_PRIMARY, &iter);
7206 static void compose_spell_menu_changed(void *data)
7208 Compose *compose = (Compose *)data;
7210 GtkWidget *menuitem;
7211 GtkWidget *parent_item;
7212 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7215 if (compose->gtkaspell == NULL)
7218 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7219 "/Menu/Spelling/Options");
7221 /* setting the submenu removes /Spelling/Options from the factory
7222 * so we need to save it */
7224 if (parent_item == NULL) {
7225 parent_item = compose->aspell_options_menu;
7226 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7228 compose->aspell_options_menu = parent_item;
7230 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7232 spell_menu = g_slist_reverse(spell_menu);
7233 for (items = spell_menu;
7234 items; items = items->next) {
7235 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7236 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7237 gtk_widget_show(GTK_WIDGET(menuitem));
7239 g_slist_free(spell_menu);
7241 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7242 gtk_widget_show(parent_item);
7245 static void compose_dict_changed(void *data)
7247 Compose *compose = (Compose *) data;
7249 if(compose->gtkaspell &&
7250 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7253 gtkaspell_highlight_all(compose->gtkaspell);
7254 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7258 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7260 Compose *compose = (Compose *)data;
7261 GdkEventButton event;
7264 event.time = gtk_get_current_event_time();
7268 return text_clicked(compose->text, &event, compose);
7271 static gboolean compose_force_window_origin = TRUE;
7272 static Compose *compose_create(PrefsAccount *account,
7281 GtkWidget *handlebox;
7283 GtkWidget *notebook;
7285 GtkWidget *attach_hbox;
7286 GtkWidget *attach_lab1;
7287 GtkWidget *attach_lab2;
7292 GtkWidget *subject_hbox;
7293 GtkWidget *subject_frame;
7294 GtkWidget *subject_entry;
7298 GtkWidget *edit_vbox;
7299 GtkWidget *ruler_hbox;
7301 GtkWidget *scrolledwin;
7303 GtkTextBuffer *buffer;
7304 GtkClipboard *clipboard;
7306 UndoMain *undostruct;
7308 GtkWidget *popupmenu;
7309 GtkWidget *tmpl_menu;
7310 GtkActionGroup *action_group = NULL;
7313 GtkAspell * gtkaspell = NULL;
7316 static GdkGeometry geometry;
7318 cm_return_val_if_fail(account != NULL, NULL);
7320 debug_print("Creating compose window...\n");
7321 compose = g_new0(Compose, 1);
7323 compose->batch = batch;
7324 compose->account = account;
7325 compose->folder = folder;
7327 compose->mutex = cm_mutex_new();
7328 compose->set_cursor_pos = -1;
7330 #if !(GTK_CHECK_VERSION(2,12,0))
7331 compose->tooltips = tips;
7334 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7336 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7337 gtk_widget_set_size_request(window, prefs_common.compose_width,
7338 prefs_common.compose_height);
7340 if (!geometry.max_width) {
7341 geometry.max_width = gdk_screen_width();
7342 geometry.max_height = gdk_screen_height();
7345 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7346 &geometry, GDK_HINT_MAX_SIZE);
7347 if (!geometry.min_width) {
7348 geometry.min_width = 600;
7349 geometry.min_height = 440;
7351 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7352 &geometry, GDK_HINT_MIN_SIZE);
7354 #ifndef GENERIC_UMPC
7355 if (compose_force_window_origin)
7356 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7357 prefs_common.compose_y);
7359 g_signal_connect(G_OBJECT(window), "delete_event",
7360 G_CALLBACK(compose_delete_cb), compose);
7361 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7362 gtk_widget_realize(window);
7364 gtkut_widget_set_composer_icon(window);
7366 vbox = gtk_vbox_new(FALSE, 0);
7367 gtk_container_add(GTK_CONTAINER(window), vbox);
7369 compose->ui_manager = gtk_ui_manager_new();
7370 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7371 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7372 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7373 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7374 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7375 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7376 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7377 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7378 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7379 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7383 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7384 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7386 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7389 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7393 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7394 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7396 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7400 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7407 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7409 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7411 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7416 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7418 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7420 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7422 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7423 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7424 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7431 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7432 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7433 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7446 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7497 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)
7498 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)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7504 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)
7505 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)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7510 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)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7514 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)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7520 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)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7526 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)
7527 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)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7533 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7540 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)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7543 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7544 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7548 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7549 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7550 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7551 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7552 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7553 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7556 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7558 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7559 gtk_widget_show_all(menubar);
7561 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7562 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7564 if (prefs_common.toolbar_detachable) {
7565 handlebox = gtk_handle_box_new();
7567 handlebox = gtk_hbox_new(FALSE, 0);
7569 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7571 gtk_widget_realize(handlebox);
7572 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7575 vbox2 = gtk_vbox_new(FALSE, 2);
7576 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7577 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7580 notebook = gtk_notebook_new();
7581 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7582 gtk_widget_show(notebook);
7584 /* header labels and entries */
7585 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7586 compose_create_header(compose),
7587 gtk_label_new_with_mnemonic(_("Hea_der")));
7588 /* attachment list */
7589 attach_hbox = gtk_hbox_new(FALSE, 0);
7590 gtk_widget_show(attach_hbox);
7592 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7593 gtk_widget_show(attach_lab1);
7594 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7596 attach_lab2 = gtk_label_new("");
7597 gtk_widget_show(attach_lab2);
7598 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7600 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7601 compose_create_attach(compose),
7604 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7605 compose_create_others(compose),
7606 gtk_label_new_with_mnemonic(_("Othe_rs")));
7609 subject_hbox = gtk_hbox_new(FALSE, 0);
7610 gtk_widget_show(subject_hbox);
7612 subject_frame = gtk_frame_new(NULL);
7613 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7614 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7615 gtk_widget_show(subject_frame);
7617 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7618 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7619 gtk_widget_show(subject);
7621 label = gtk_label_new(_("Subject:"));
7622 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7623 gtk_widget_show(label);
7626 subject_entry = claws_spell_entry_new();
7628 subject_entry = gtk_entry_new();
7630 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7631 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7632 G_CALLBACK(compose_grab_focus_cb), compose);
7633 gtk_widget_show(subject_entry);
7634 compose->subject_entry = subject_entry;
7635 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7637 edit_vbox = gtk_vbox_new(FALSE, 0);
7639 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7642 ruler_hbox = gtk_hbox_new(FALSE, 0);
7643 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7645 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7646 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7647 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7651 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7652 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7653 GTK_POLICY_AUTOMATIC,
7654 GTK_POLICY_AUTOMATIC);
7655 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7657 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7659 text = gtk_text_view_new();
7660 if (prefs_common.show_compose_margin) {
7661 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7662 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7664 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7665 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7666 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7667 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7668 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7670 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7671 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7672 G_CALLBACK(compose_notebook_size_alloc), compose);
7673 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7674 G_CALLBACK(compose_edit_size_alloc),
7676 g_signal_connect(G_OBJECT(buffer), "changed",
7677 G_CALLBACK(compose_changed_cb), compose);
7678 g_signal_connect(G_OBJECT(text), "grab_focus",
7679 G_CALLBACK(compose_grab_focus_cb), compose);
7680 g_signal_connect(G_OBJECT(buffer), "insert_text",
7681 G_CALLBACK(text_inserted), compose);
7682 g_signal_connect(G_OBJECT(text), "button_press_event",
7683 G_CALLBACK(text_clicked), compose);
7684 g_signal_connect(G_OBJECT(text), "popup-menu",
7685 G_CALLBACK(compose_popup_menu), compose);
7686 g_signal_connect(G_OBJECT(subject_entry), "changed",
7687 G_CALLBACK(compose_changed_cb), compose);
7688 g_signal_connect(G_OBJECT(subject_entry), "activate",
7689 G_CALLBACK(compose_subject_entry_activated), compose);
7692 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7693 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7694 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7695 g_signal_connect(G_OBJECT(text), "drag_data_received",
7696 G_CALLBACK(compose_insert_drag_received_cb),
7698 g_signal_connect(G_OBJECT(text), "drag-drop",
7699 G_CALLBACK(compose_drag_drop),
7701 g_signal_connect(G_OBJECT(text), "key-press-event",
7702 G_CALLBACK(completion_set_focus_to_subject),
7704 gtk_widget_show_all(vbox);
7706 /* pane between attach clist and text */
7707 paned = gtk_vpaned_new();
7708 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7709 gtk_paned_add1(GTK_PANED(paned), notebook);
7710 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7711 gtk_widget_show_all(paned);
7714 if (prefs_common.textfont) {
7715 PangoFontDescription *font_desc;
7717 font_desc = pango_font_description_from_string
7718 (prefs_common.textfont);
7720 gtk_widget_modify_font(text, font_desc);
7721 pango_font_description_free(font_desc);
7725 gtk_action_group_add_actions(action_group, compose_popup_entries,
7726 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7734 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7736 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7737 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7738 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7740 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7742 undostruct = undo_init(text);
7743 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7746 address_completion_start(window);
7748 compose->window = window;
7749 compose->vbox = vbox;
7750 compose->menubar = menubar;
7751 compose->handlebox = handlebox;
7753 compose->vbox2 = vbox2;
7755 compose->paned = paned;
7757 compose->attach_label = attach_lab2;
7759 compose->notebook = notebook;
7760 compose->edit_vbox = edit_vbox;
7761 compose->ruler_hbox = ruler_hbox;
7762 compose->ruler = ruler;
7763 compose->scrolledwin = scrolledwin;
7764 compose->text = text;
7766 compose->focused_editable = NULL;
7768 compose->popupmenu = popupmenu;
7770 compose->tmpl_menu = tmpl_menu;
7772 compose->mode = mode;
7773 compose->rmode = mode;
7775 compose->targetinfo = NULL;
7776 compose->replyinfo = NULL;
7777 compose->fwdinfo = NULL;
7779 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7780 g_str_equal, (GDestroyNotify) g_free, NULL);
7782 compose->replyto = NULL;
7784 compose->bcc = NULL;
7785 compose->followup_to = NULL;
7787 compose->ml_post = NULL;
7789 compose->inreplyto = NULL;
7790 compose->references = NULL;
7791 compose->msgid = NULL;
7792 compose->boundary = NULL;
7794 compose->autowrap = prefs_common.autowrap;
7795 compose->autoindent = prefs_common.auto_indent;
7796 compose->use_signing = FALSE;
7797 compose->use_encryption = FALSE;
7798 compose->privacy_system = NULL;
7800 compose->modified = FALSE;
7802 compose->return_receipt = FALSE;
7804 compose->to_list = NULL;
7805 compose->newsgroup_list = NULL;
7807 compose->undostruct = undostruct;
7809 compose->sig_str = NULL;
7811 compose->exteditor_file = NULL;
7812 compose->exteditor_pid = -1;
7813 compose->exteditor_tag = -1;
7814 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7817 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7818 if (mode != COMPOSE_REDIRECT) {
7819 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7820 strcmp(prefs_common.dictionary, "")) {
7821 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7822 prefs_common.alt_dictionary,
7823 conv_get_locale_charset_str(),
7824 prefs_common.misspelled_col,
7825 prefs_common.check_while_typing,
7826 prefs_common.recheck_when_changing_dict,
7827 prefs_common.use_alternate,
7828 prefs_common.use_both_dicts,
7829 GTK_TEXT_VIEW(text),
7830 GTK_WINDOW(compose->window),
7831 compose_dict_changed,
7832 compose_spell_menu_changed,
7835 alertpanel_error(_("Spell checker could not "
7837 gtkaspell_checkers_strerror());
7838 gtkaspell_checkers_reset_error();
7840 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7844 compose->gtkaspell = gtkaspell;
7845 compose_spell_menu_changed(compose);
7846 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7849 compose_select_account(compose, account, TRUE);
7851 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7852 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7854 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7855 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7857 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7858 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7860 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7861 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7863 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7864 if (account->protocol != A_NNTP)
7865 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7866 prefs_common_translated_header_name("To:"));
7868 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7869 prefs_common_translated_header_name("Newsgroups:"));
7871 #ifndef USE_NEW_ADDRBOOK
7872 addressbook_set_target_compose(compose);
7874 if (mode != COMPOSE_REDIRECT)
7875 compose_set_template_menu(compose);
7877 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7880 compose_list = g_list_append(compose_list, compose);
7882 if (!prefs_common.show_ruler)
7883 gtk_widget_hide(ruler_hbox);
7885 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7888 compose->priority = PRIORITY_NORMAL;
7889 compose_update_priority_menu_item(compose);
7891 compose_set_out_encoding(compose);
7894 compose_update_actions_menu(compose);
7896 /* Privacy Systems menu */
7897 compose_update_privacy_systems_menu(compose);
7899 activate_privacy_system(compose, account, TRUE);
7900 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7902 gtk_widget_realize(window);
7904 gtk_widget_show(window);
7910 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7915 GtkWidget *optmenubox;
7918 GtkWidget *from_name = NULL;
7919 #if !(GTK_CHECK_VERSION(2,12,0))
7920 GtkTooltips *tips = compose->tooltips;
7923 gint num = 0, def_menu = 0;
7925 accounts = account_get_list();
7926 cm_return_val_if_fail(accounts != NULL, NULL);
7928 optmenubox = gtk_event_box_new();
7929 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7930 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7932 hbox = gtk_hbox_new(FALSE, 6);
7933 from_name = gtk_entry_new();
7935 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7936 G_CALLBACK(compose_grab_focus_cb), compose);
7938 for (; accounts != NULL; accounts = accounts->next, num++) {
7939 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7940 gchar *name, *from = NULL;
7942 if (ac == compose->account) def_menu = num;
7944 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7947 if (ac == compose->account) {
7948 if (ac->name && *ac->name) {
7950 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7951 from = g_strdup_printf("%s <%s>",
7953 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7955 from = g_strdup_printf("%s",
7957 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7960 COMBOBOX_ADD(menu, name, ac->account_id);
7965 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7967 g_signal_connect(G_OBJECT(optmenu), "changed",
7968 G_CALLBACK(account_activated),
7970 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7971 G_CALLBACK(compose_entry_popup_extend),
7974 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7975 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7977 CLAWS_SET_TIP(optmenubox,
7978 _("Account to use for this email"));
7979 CLAWS_SET_TIP(from_name,
7980 _("Sender address to be used"));
7982 compose->account_combo = optmenu;
7983 compose->from_name = from_name;
7988 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7990 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7991 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7992 Compose *compose = (Compose *) data;
7994 compose->priority = value;
7998 static void compose_reply_change_mode(Compose *compose,
8001 gboolean was_modified = compose->modified;
8003 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8005 cm_return_if_fail(compose->replyinfo != NULL);
8007 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8009 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8011 if (action == COMPOSE_REPLY_TO_ALL)
8013 if (action == COMPOSE_REPLY_TO_SENDER)
8015 if (action == COMPOSE_REPLY_TO_LIST)
8018 compose_remove_header_entries(compose);
8019 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8020 if (compose->account->set_autocc && compose->account->auto_cc)
8021 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8023 if (compose->account->set_autobcc && compose->account->auto_bcc)
8024 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8026 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8027 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8028 compose_show_first_last_header(compose, TRUE);
8029 compose->modified = was_modified;
8030 compose_set_title(compose);
8033 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8035 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8036 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8037 Compose *compose = (Compose *) data;
8040 compose_reply_change_mode(compose, value);
8043 static void compose_update_priority_menu_item(Compose * compose)
8045 GtkWidget *menuitem = NULL;
8046 switch (compose->priority) {
8047 case PRIORITY_HIGHEST:
8048 menuitem = gtk_ui_manager_get_widget
8049 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8052 menuitem = gtk_ui_manager_get_widget
8053 (compose->ui_manager, "/Menu/Options/Priority/High");
8055 case PRIORITY_NORMAL:
8056 menuitem = gtk_ui_manager_get_widget
8057 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8060 menuitem = gtk_ui_manager_get_widget
8061 (compose->ui_manager, "/Menu/Options/Priority/Low");
8063 case PRIORITY_LOWEST:
8064 menuitem = gtk_ui_manager_get_widget
8065 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8068 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8071 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8073 Compose *compose = (Compose *) data;
8075 gboolean can_sign = FALSE, can_encrypt = FALSE;
8077 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8079 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8082 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8083 g_free(compose->privacy_system);
8084 compose->privacy_system = NULL;
8085 if (systemid != NULL) {
8086 compose->privacy_system = g_strdup(systemid);
8088 can_sign = privacy_system_can_sign(systemid);
8089 can_encrypt = privacy_system_can_encrypt(systemid);
8092 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8094 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8095 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8098 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8100 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8101 GtkWidget *menuitem = NULL;
8102 GList *children, *amenu;
8103 gboolean can_sign = FALSE, can_encrypt = FALSE;
8104 gboolean found = FALSE;
8106 if (compose->privacy_system != NULL) {
8108 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8109 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8110 cm_return_if_fail(menuitem != NULL);
8112 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8115 while (amenu != NULL) {
8116 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8117 if (systemid != NULL) {
8118 if (strcmp(systemid, compose->privacy_system) == 0 &&
8119 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8120 menuitem = GTK_WIDGET(amenu->data);
8122 can_sign = privacy_system_can_sign(systemid);
8123 can_encrypt = privacy_system_can_encrypt(systemid);
8127 } else if (strlen(compose->privacy_system) == 0 &&
8128 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8129 menuitem = GTK_WIDGET(amenu->data);
8132 can_encrypt = FALSE;
8137 amenu = amenu->next;
8139 g_list_free(children);
8140 if (menuitem != NULL)
8141 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8143 if (warn && !found && strlen(compose->privacy_system)) {
8144 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8145 "will not be able to sign or encrypt this message."),
8146 compose->privacy_system);
8150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8151 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8154 static void compose_set_out_encoding(Compose *compose)
8156 CharSet out_encoding;
8157 const gchar *branch = NULL;
8158 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8160 switch(out_encoding) {
8161 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8162 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8163 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8164 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8165 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8166 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8167 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8168 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8169 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8170 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8171 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8172 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8173 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8174 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8175 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8176 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8177 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8178 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8179 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8180 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8181 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8182 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8183 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8184 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8185 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8186 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8187 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8188 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8189 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8190 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8191 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8192 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8193 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8195 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8198 static void compose_set_template_menu(Compose *compose)
8200 GSList *tmpl_list, *cur;
8204 tmpl_list = template_get_config();
8206 menu = gtk_menu_new();
8208 gtk_menu_set_accel_group (GTK_MENU (menu),
8209 gtk_ui_manager_get_accel_group(compose->ui_manager));
8210 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8211 Template *tmpl = (Template *)cur->data;
8212 gchar *accel_path = NULL;
8213 item = gtk_menu_item_new_with_label(tmpl->name);
8214 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8215 g_signal_connect(G_OBJECT(item), "activate",
8216 G_CALLBACK(compose_template_activate_cb),
8218 g_object_set_data(G_OBJECT(item), "template", tmpl);
8219 gtk_widget_show(item);
8220 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8221 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8225 gtk_widget_show(menu);
8226 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8229 void compose_update_actions_menu(Compose *compose)
8231 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8234 static void compose_update_privacy_systems_menu(Compose *compose)
8236 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8237 GSList *systems, *cur;
8239 GtkWidget *system_none;
8241 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8242 GtkWidget *privacy_menu = gtk_menu_new();
8244 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8245 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8247 g_signal_connect(G_OBJECT(system_none), "activate",
8248 G_CALLBACK(compose_set_privacy_system_cb), compose);
8250 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8251 gtk_widget_show(system_none);
8253 systems = privacy_get_system_ids();
8254 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8255 gchar *systemid = cur->data;
8257 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8258 widget = gtk_radio_menu_item_new_with_label(group,
8259 privacy_system_get_name(systemid));
8260 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8261 g_strdup(systemid), g_free);
8262 g_signal_connect(G_OBJECT(widget), "activate",
8263 G_CALLBACK(compose_set_privacy_system_cb), compose);
8265 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8266 gtk_widget_show(widget);
8269 g_slist_free(systems);
8270 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8271 gtk_widget_show_all(privacy_menu);
8272 gtk_widget_show_all(privacy_menuitem);
8275 void compose_reflect_prefs_all(void)
8280 for (cur = compose_list; cur != NULL; cur = cur->next) {
8281 compose = (Compose *)cur->data;
8282 compose_set_template_menu(compose);
8286 void compose_reflect_prefs_pixmap_theme(void)
8291 for (cur = compose_list; cur != NULL; cur = cur->next) {
8292 compose = (Compose *)cur->data;
8293 toolbar_update(TOOLBAR_COMPOSE, compose);
8297 static const gchar *compose_quote_char_from_context(Compose *compose)
8299 const gchar *qmark = NULL;
8301 cm_return_val_if_fail(compose != NULL, NULL);
8303 switch (compose->mode) {
8304 /* use forward-specific quote char */
8305 case COMPOSE_FORWARD:
8306 case COMPOSE_FORWARD_AS_ATTACH:
8307 case COMPOSE_FORWARD_INLINE:
8308 if (compose->folder && compose->folder->prefs &&
8309 compose->folder->prefs->forward_with_format)
8310 qmark = compose->folder->prefs->forward_quotemark;
8311 else if (compose->account->forward_with_format)
8312 qmark = compose->account->forward_quotemark;
8314 qmark = prefs_common.fw_quotemark;
8317 /* use reply-specific quote char in all other modes */
8319 if (compose->folder && compose->folder->prefs &&
8320 compose->folder->prefs->reply_with_format)
8321 qmark = compose->folder->prefs->reply_quotemark;
8322 else if (compose->account->reply_with_format)
8323 qmark = compose->account->reply_quotemark;
8325 qmark = prefs_common.quotemark;
8329 if (qmark == NULL || *qmark == '\0')
8335 static void compose_template_apply(Compose *compose, Template *tmpl,
8339 GtkTextBuffer *buffer;
8343 gchar *parsed_str = NULL;
8344 gint cursor_pos = 0;
8345 const gchar *err_msg = _("The body of the template has an error at line %d.");
8348 /* process the body */
8350 text = GTK_TEXT_VIEW(compose->text);
8351 buffer = gtk_text_view_get_buffer(text);
8354 qmark = compose_quote_char_from_context(compose);
8356 if (compose->replyinfo != NULL) {
8359 gtk_text_buffer_set_text(buffer, "", -1);
8360 mark = gtk_text_buffer_get_insert(buffer);
8361 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8363 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8364 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8366 } else if (compose->fwdinfo != NULL) {
8369 gtk_text_buffer_set_text(buffer, "", -1);
8370 mark = gtk_text_buffer_get_insert(buffer);
8371 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8373 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8374 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8377 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8379 GtkTextIter start, end;
8382 gtk_text_buffer_get_start_iter(buffer, &start);
8383 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8384 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8386 /* clear the buffer now */
8388 gtk_text_buffer_set_text(buffer, "", -1);
8390 parsed_str = compose_quote_fmt(compose, dummyinfo,
8391 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8392 procmsg_msginfo_free( dummyinfo );
8398 gtk_text_buffer_set_text(buffer, "", -1);
8399 mark = gtk_text_buffer_get_insert(buffer);
8400 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8403 if (replace && parsed_str && compose->account->auto_sig)
8404 compose_insert_sig(compose, FALSE);
8406 if (replace && parsed_str) {
8407 gtk_text_buffer_get_start_iter(buffer, &iter);
8408 gtk_text_buffer_place_cursor(buffer, &iter);
8412 cursor_pos = quote_fmt_get_cursor_pos();
8413 compose->set_cursor_pos = cursor_pos;
8414 if (cursor_pos == -1)
8416 gtk_text_buffer_get_start_iter(buffer, &iter);
8417 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8418 gtk_text_buffer_place_cursor(buffer, &iter);
8421 /* process the other fields */
8423 compose_template_apply_fields(compose, tmpl);
8424 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8425 quote_fmt_reset_vartable();
8426 compose_changed_cb(NULL, compose);
8429 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8430 gtkaspell_highlight_all(compose->gtkaspell);
8434 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8436 MsgInfo* dummyinfo = NULL;
8437 MsgInfo *msginfo = NULL;
8440 if (compose->replyinfo != NULL)
8441 msginfo = compose->replyinfo;
8442 else if (compose->fwdinfo != NULL)
8443 msginfo = compose->fwdinfo;
8445 dummyinfo = compose_msginfo_new_from_compose(compose);
8446 msginfo = dummyinfo;
8449 if (tmpl->from && *tmpl->from != '\0') {
8451 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8452 compose->gtkaspell);
8454 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8456 quote_fmt_scan_string(tmpl->from);
8459 buf = quote_fmt_get_buffer();
8461 alertpanel_error(_("Template From format error."));
8463 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8467 if (tmpl->to && *tmpl->to != '\0') {
8469 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8470 compose->gtkaspell);
8472 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8474 quote_fmt_scan_string(tmpl->to);
8477 buf = quote_fmt_get_buffer();
8479 alertpanel_error(_("Template To format error."));
8481 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8485 if (tmpl->cc && *tmpl->cc != '\0') {
8487 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8488 compose->gtkaspell);
8490 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8492 quote_fmt_scan_string(tmpl->cc);
8495 buf = quote_fmt_get_buffer();
8497 alertpanel_error(_("Template Cc format error."));
8499 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8503 if (tmpl->bcc && *tmpl->bcc != '\0') {
8505 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8506 compose->gtkaspell);
8508 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8510 quote_fmt_scan_string(tmpl->bcc);
8513 buf = quote_fmt_get_buffer();
8515 alertpanel_error(_("Template Bcc format error."));
8517 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8521 /* process the subject */
8522 if (tmpl->subject && *tmpl->subject != '\0') {
8524 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8525 compose->gtkaspell);
8527 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8529 quote_fmt_scan_string(tmpl->subject);
8532 buf = quote_fmt_get_buffer();
8534 alertpanel_error(_("Template subject format error."));
8536 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8540 procmsg_msginfo_free( dummyinfo );
8543 static void compose_destroy(Compose *compose)
8545 GtkAllocation allocation;
8546 GtkTextBuffer *buffer;
8547 GtkClipboard *clipboard;
8549 compose_list = g_list_remove(compose_list, compose);
8551 if (compose->updating) {
8552 debug_print("danger, not destroying anything now\n");
8553 compose->deferred_destroy = TRUE;
8556 /* NOTE: address_completion_end() does nothing with the window
8557 * however this may change. */
8558 address_completion_end(compose->window);
8560 slist_free_strings_full(compose->to_list);
8561 slist_free_strings_full(compose->newsgroup_list);
8562 slist_free_strings_full(compose->header_list);
8564 slist_free_strings_full(extra_headers);
8565 extra_headers = NULL;
8567 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8569 g_hash_table_destroy(compose->email_hashtable);
8571 procmsg_msginfo_free(compose->targetinfo);
8572 procmsg_msginfo_free(compose->replyinfo);
8573 procmsg_msginfo_free(compose->fwdinfo);
8575 g_free(compose->replyto);
8576 g_free(compose->cc);
8577 g_free(compose->bcc);
8578 g_free(compose->newsgroups);
8579 g_free(compose->followup_to);
8581 g_free(compose->ml_post);
8583 g_free(compose->inreplyto);
8584 g_free(compose->references);
8585 g_free(compose->msgid);
8586 g_free(compose->boundary);
8588 g_free(compose->redirect_filename);
8589 if (compose->undostruct)
8590 undo_destroy(compose->undostruct);
8592 g_free(compose->sig_str);
8594 g_free(compose->exteditor_file);
8596 g_free(compose->orig_charset);
8598 g_free(compose->privacy_system);
8600 #ifndef USE_NEW_ADDRBOOK
8601 if (addressbook_get_target_compose() == compose)
8602 addressbook_set_target_compose(NULL);
8605 if (compose->gtkaspell) {
8606 gtkaspell_delete(compose->gtkaspell);
8607 compose->gtkaspell = NULL;
8611 if (!compose->batch) {
8612 gtk_widget_get_allocation(compose->window, &allocation);
8613 prefs_common.compose_width = allocation.width;
8614 prefs_common.compose_height = allocation.height;
8617 if (!gtk_widget_get_parent(compose->paned))
8618 gtk_widget_destroy(compose->paned);
8619 gtk_widget_destroy(compose->popupmenu);
8621 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8622 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8623 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8625 gtk_widget_destroy(compose->window);
8626 toolbar_destroy(compose->toolbar);
8627 g_free(compose->toolbar);
8628 cm_mutex_free(compose->mutex);
8632 static void compose_attach_info_free(AttachInfo *ainfo)
8634 g_free(ainfo->file);
8635 g_free(ainfo->content_type);
8636 g_free(ainfo->name);
8637 g_free(ainfo->charset);
8641 static void compose_attach_update_label(Compose *compose)
8646 GtkTreeModel *model;
8651 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8652 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8653 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8657 while(gtk_tree_model_iter_next(model, &iter))
8660 text = g_strdup_printf("(%d)", i);
8661 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8665 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8667 Compose *compose = (Compose *)data;
8668 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8669 GtkTreeSelection *selection;
8671 GtkTreeModel *model;
8673 selection = gtk_tree_view_get_selection(tree_view);
8674 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8679 for (cur = sel; cur != NULL; cur = cur->next) {
8680 GtkTreePath *path = cur->data;
8681 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8684 gtk_tree_path_free(path);
8687 for (cur = sel; cur != NULL; cur = cur->next) {
8688 GtkTreeRowReference *ref = cur->data;
8689 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8692 if (gtk_tree_model_get_iter(model, &iter, path))
8693 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8695 gtk_tree_path_free(path);
8696 gtk_tree_row_reference_free(ref);
8700 compose_attach_update_label(compose);
8703 static struct _AttachProperty
8706 GtkWidget *mimetype_entry;
8707 GtkWidget *encoding_optmenu;
8708 GtkWidget *path_entry;
8709 GtkWidget *filename_entry;
8711 GtkWidget *cancel_btn;
8714 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8716 gtk_tree_path_free((GtkTreePath *)ptr);
8719 static void compose_attach_property(GtkAction *action, gpointer data)
8721 Compose *compose = (Compose *)data;
8722 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8724 GtkComboBox *optmenu;
8725 GtkTreeSelection *selection;
8727 GtkTreeModel *model;
8730 static gboolean cancelled;
8732 /* only if one selected */
8733 selection = gtk_tree_view_get_selection(tree_view);
8734 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8737 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8741 path = (GtkTreePath *) sel->data;
8742 gtk_tree_model_get_iter(model, &iter, path);
8743 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8746 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8752 if (!attach_prop.window)
8753 compose_attach_property_create(&cancelled);
8754 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8755 gtk_widget_grab_focus(attach_prop.ok_btn);
8756 gtk_widget_show(attach_prop.window);
8757 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8758 GTK_WINDOW(compose->window));
8760 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8761 if (ainfo->encoding == ENC_UNKNOWN)
8762 combobox_select_by_data(optmenu, ENC_BASE64);
8764 combobox_select_by_data(optmenu, ainfo->encoding);
8766 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8767 ainfo->content_type ? ainfo->content_type : "");
8768 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8769 ainfo->file ? ainfo->file : "");
8770 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8771 ainfo->name ? ainfo->name : "");
8774 const gchar *entry_text;
8776 gchar *cnttype = NULL;
8783 gtk_widget_hide(attach_prop.window);
8784 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8789 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8790 if (*entry_text != '\0') {
8793 text = g_strstrip(g_strdup(entry_text));
8794 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8795 cnttype = g_strdup(text);
8798 alertpanel_error(_("Invalid MIME type."));
8804 ainfo->encoding = combobox_get_active_data(optmenu);
8806 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8807 if (*entry_text != '\0') {
8808 if (is_file_exist(entry_text) &&
8809 (size = get_file_size(entry_text)) > 0)
8810 file = g_strdup(entry_text);
8813 (_("File doesn't exist or is empty."));
8819 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8820 if (*entry_text != '\0') {
8821 g_free(ainfo->name);
8822 ainfo->name = g_strdup(entry_text);
8826 g_free(ainfo->content_type);
8827 ainfo->content_type = cnttype;
8830 g_free(ainfo->file);
8834 ainfo->size = (goffset)size;
8836 /* update tree store */
8837 text = to_human_readable(ainfo->size);
8838 gtk_tree_model_get_iter(model, &iter, path);
8839 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8840 COL_MIMETYPE, ainfo->content_type,
8842 COL_NAME, ainfo->name,
8843 COL_CHARSET, ainfo->charset,
8849 gtk_tree_path_free(path);
8852 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8854 label = gtk_label_new(str); \
8855 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8856 GTK_FILL, 0, 0, 0); \
8857 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8859 entry = gtk_entry_new(); \
8860 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8861 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8864 static void compose_attach_property_create(gboolean *cancelled)
8870 GtkWidget *mimetype_entry;
8873 GtkListStore *optmenu_menu;
8874 GtkWidget *path_entry;
8875 GtkWidget *filename_entry;
8878 GtkWidget *cancel_btn;
8879 GList *mime_type_list, *strlist;
8882 debug_print("Creating attach_property window...\n");
8884 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8885 gtk_widget_set_size_request(window, 480, -1);
8886 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8887 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8888 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8889 g_signal_connect(G_OBJECT(window), "delete_event",
8890 G_CALLBACK(attach_property_delete_event),
8892 g_signal_connect(G_OBJECT(window), "key_press_event",
8893 G_CALLBACK(attach_property_key_pressed),
8896 vbox = gtk_vbox_new(FALSE, 8);
8897 gtk_container_add(GTK_CONTAINER(window), vbox);
8899 table = gtk_table_new(4, 2, FALSE);
8900 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8901 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8902 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8904 label = gtk_label_new(_("MIME type"));
8905 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8907 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8908 #if !GTK_CHECK_VERSION(2, 24, 0)
8909 mimetype_entry = gtk_combo_box_entry_new_text();
8911 mimetype_entry = gtk_combo_box_text_new_with_entry();
8913 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8914 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8916 /* stuff with list */
8917 mime_type_list = procmime_get_mime_type_list();
8919 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8920 MimeType *type = (MimeType *) mime_type_list->data;
8923 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8925 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8928 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8929 (GCompareFunc)strcmp2);
8932 for (mime_type_list = strlist; mime_type_list != NULL;
8933 mime_type_list = mime_type_list->next) {
8934 #if !GTK_CHECK_VERSION(2, 24, 0)
8935 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8937 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8939 g_free(mime_type_list->data);
8941 g_list_free(strlist);
8942 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8943 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8945 label = gtk_label_new(_("Encoding"));
8946 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8948 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8950 hbox = gtk_hbox_new(FALSE, 0);
8951 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8952 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8954 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8955 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8957 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8958 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8959 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8960 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8961 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8963 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8965 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8966 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8968 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8969 &ok_btn, GTK_STOCK_OK,
8971 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8972 gtk_widget_grab_default(ok_btn);
8974 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8975 G_CALLBACK(attach_property_ok),
8977 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8978 G_CALLBACK(attach_property_cancel),
8981 gtk_widget_show_all(vbox);
8983 attach_prop.window = window;
8984 attach_prop.mimetype_entry = mimetype_entry;
8985 attach_prop.encoding_optmenu = optmenu;
8986 attach_prop.path_entry = path_entry;
8987 attach_prop.filename_entry = filename_entry;
8988 attach_prop.ok_btn = ok_btn;
8989 attach_prop.cancel_btn = cancel_btn;
8992 #undef SET_LABEL_AND_ENTRY
8994 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9000 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9006 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9007 gboolean *cancelled)
9015 static gboolean attach_property_key_pressed(GtkWidget *widget,
9017 gboolean *cancelled)
9019 if (event && event->keyval == GDK_KEY_Escape) {
9023 if (event && event->keyval == GDK_KEY_Return) {
9031 static void compose_exec_ext_editor(Compose *compose)
9038 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9039 G_DIR_SEPARATOR, compose);
9041 if (pipe(pipe_fds) < 0) {
9047 if ((pid = fork()) < 0) {
9054 /* close the write side of the pipe */
9057 compose->exteditor_file = g_strdup(tmp);
9058 compose->exteditor_pid = pid;
9060 compose_set_ext_editor_sensitive(compose, FALSE);
9063 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9065 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9067 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9071 } else { /* process-monitoring process */
9077 /* close the read side of the pipe */
9080 if (compose_write_body_to_file(compose, tmp) < 0) {
9081 fd_write_all(pipe_fds[1], "2\n", 2);
9085 pid_ed = compose_exec_ext_editor_real(tmp);
9087 fd_write_all(pipe_fds[1], "1\n", 2);
9091 /* wait until editor is terminated */
9092 waitpid(pid_ed, NULL, 0);
9094 fd_write_all(pipe_fds[1], "0\n", 2);
9101 #endif /* G_OS_UNIX */
9105 static gint compose_exec_ext_editor_real(const gchar *file)
9112 cm_return_val_if_fail(file != NULL, -1);
9114 if ((pid = fork()) < 0) {
9119 if (pid != 0) return pid;
9121 /* grandchild process */
9123 if (setpgid(0, getppid()))
9126 if (prefs_common_get_ext_editor_cmd() &&
9127 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9128 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9129 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9131 if (prefs_common_get_ext_editor_cmd())
9132 g_warning("External editor command-line is invalid: '%s'\n",
9133 prefs_common_get_ext_editor_cmd());
9134 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9137 cmdline = strsplit_with_quote(buf, " ", 1024);
9138 execvp(cmdline[0], cmdline);
9141 g_strfreev(cmdline);
9146 static gboolean compose_ext_editor_kill(Compose *compose)
9148 pid_t pgid = compose->exteditor_pid * -1;
9151 ret = kill(pgid, 0);
9153 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9157 msg = g_strdup_printf
9158 (_("The external editor is still working.\n"
9159 "Force terminating the process?\n"
9160 "process group id: %d"), -pgid);
9161 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9162 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9166 if (val == G_ALERTALTERNATE) {
9167 g_source_remove(compose->exteditor_tag);
9168 g_io_channel_shutdown(compose->exteditor_ch,
9170 g_io_channel_unref(compose->exteditor_ch);
9172 if (kill(pgid, SIGTERM) < 0) perror("kill");
9173 waitpid(compose->exteditor_pid, NULL, 0);
9175 g_warning("Terminated process group id: %d", -pgid);
9176 g_warning("Temporary file: %s",
9177 compose->exteditor_file);
9179 compose_set_ext_editor_sensitive(compose, TRUE);
9181 g_free(compose->exteditor_file);
9182 compose->exteditor_file = NULL;
9183 compose->exteditor_pid = -1;
9184 compose->exteditor_ch = NULL;
9185 compose->exteditor_tag = -1;
9193 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9197 Compose *compose = (Compose *)data;
9200 debug_print("Compose: input from monitoring process\n");
9202 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9204 g_io_channel_shutdown(source, FALSE, NULL);
9205 g_io_channel_unref(source);
9207 waitpid(compose->exteditor_pid, NULL, 0);
9209 if (buf[0] == '0') { /* success */
9210 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9211 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9213 gtk_text_buffer_set_text(buffer, "", -1);
9214 compose_insert_file(compose, compose->exteditor_file);
9215 compose_changed_cb(NULL, compose);
9216 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9218 if (claws_unlink(compose->exteditor_file) < 0)
9219 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9220 } else if (buf[0] == '1') { /* failed */
9221 g_warning("Couldn't exec external editor\n");
9222 if (claws_unlink(compose->exteditor_file) < 0)
9223 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9224 } else if (buf[0] == '2') {
9225 g_warning("Couldn't write to file\n");
9226 } else if (buf[0] == '3') {
9227 g_warning("Pipe read failed\n");
9230 compose_set_ext_editor_sensitive(compose, TRUE);
9232 g_free(compose->exteditor_file);
9233 compose->exteditor_file = NULL;
9234 compose->exteditor_pid = -1;
9235 compose->exteditor_ch = NULL;
9236 compose->exteditor_tag = -1;
9241 static void compose_set_ext_editor_sensitive(Compose *compose,
9244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9247 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9248 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9249 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9250 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9252 gtk_widget_set_sensitive(compose->text, sensitive);
9253 if (compose->toolbar->send_btn)
9254 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9255 if (compose->toolbar->sendl_btn)
9256 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9257 if (compose->toolbar->draft_btn)
9258 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9259 if (compose->toolbar->insert_btn)
9260 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9261 if (compose->toolbar->sig_btn)
9262 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9263 if (compose->toolbar->exteditor_btn)
9264 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9265 if (compose->toolbar->linewrap_current_btn)
9266 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9267 if (compose->toolbar->linewrap_all_btn)
9268 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9270 #endif /* G_OS_UNIX */
9273 * compose_undo_state_changed:
9275 * Change the sensivity of the menuentries undo and redo
9277 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9278 gint redo_state, gpointer data)
9280 Compose *compose = (Compose *)data;
9282 switch (undo_state) {
9283 case UNDO_STATE_TRUE:
9284 if (!undostruct->undo_state) {
9285 undostruct->undo_state = TRUE;
9286 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9289 case UNDO_STATE_FALSE:
9290 if (undostruct->undo_state) {
9291 undostruct->undo_state = FALSE;
9292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9295 case UNDO_STATE_UNCHANGED:
9297 case UNDO_STATE_REFRESH:
9298 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9301 g_warning("Undo state not recognized");
9305 switch (redo_state) {
9306 case UNDO_STATE_TRUE:
9307 if (!undostruct->redo_state) {
9308 undostruct->redo_state = TRUE;
9309 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9312 case UNDO_STATE_FALSE:
9313 if (undostruct->redo_state) {
9314 undostruct->redo_state = FALSE;
9315 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9318 case UNDO_STATE_UNCHANGED:
9320 case UNDO_STATE_REFRESH:
9321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9324 g_warning("Redo state not recognized");
9329 /* callback functions */
9331 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9332 GtkAllocation *allocation,
9335 prefs_common.compose_notebook_height = allocation->height;
9338 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9339 * includes "non-client" (windows-izm) in calculation, so this calculation
9340 * may not be accurate.
9342 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9343 GtkAllocation *allocation,
9344 GtkSHRuler *shruler)
9346 if (prefs_common.show_ruler) {
9347 gint char_width = 0, char_height = 0;
9348 gint line_width_in_chars;
9350 gtkut_get_font_size(GTK_WIDGET(widget),
9351 &char_width, &char_height);
9352 line_width_in_chars =
9353 (allocation->width - allocation->x) / char_width;
9355 /* got the maximum */
9356 gtk_shruler_set_range(GTK_SHRULER(shruler),
9357 0.0, line_width_in_chars, 0);
9366 ComposePrefType type;
9367 gboolean entry_marked;
9370 static void account_activated(GtkComboBox *optmenu, gpointer data)
9372 Compose *compose = (Compose *)data;
9375 gchar *folderidentifier;
9376 gint account_id = 0;
9379 GSList *list, *saved_list = NULL;
9380 HeaderEntryState *state;
9381 GtkRcStyle *style = NULL;
9382 #if !GTK_CHECK_VERSION(3, 0, 0)
9383 static GdkColor yellow;
9384 static gboolean color_set = FALSE;
9386 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9389 /* Get ID of active account in the combo box */
9390 menu = gtk_combo_box_get_model(optmenu);
9391 gtk_combo_box_get_active_iter(optmenu, &iter);
9392 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9394 ac = account_find_from_id(account_id);
9395 cm_return_if_fail(ac != NULL);
9397 if (ac != compose->account) {
9398 compose_select_account(compose, ac, FALSE);
9400 for (list = compose->header_list; list; list = list->next) {
9401 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9403 if (hentry->type == PREF_ACCOUNT || !list->next) {
9404 compose_destroy_headerentry(compose, hentry);
9408 state = g_malloc0(sizeof(HeaderEntryState));
9409 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9410 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9411 state->entry = gtk_editable_get_chars(
9412 GTK_EDITABLE(hentry->entry), 0, -1);
9413 state->type = hentry->type;
9415 #if !GTK_CHECK_VERSION(3, 0, 0)
9417 gdk_color_parse("#f5f6be", &yellow);
9418 color_set = gdk_colormap_alloc_color(
9419 gdk_colormap_get_system(),
9420 &yellow, FALSE, TRUE);
9424 style = gtk_widget_get_modifier_style(hentry->entry);
9425 state->entry_marked = gdk_color_equal(&yellow,
9426 &style->base[GTK_STATE_NORMAL]);
9428 saved_list = g_slist_append(saved_list, state);
9429 compose_destroy_headerentry(compose, hentry);
9432 compose->header_last = NULL;
9433 g_slist_free(compose->header_list);
9434 compose->header_list = NULL;
9435 compose->header_nextrow = 1;
9436 compose_create_header_entry(compose);
9438 if (ac->set_autocc && ac->auto_cc)
9439 compose_entry_append(compose, ac->auto_cc,
9440 COMPOSE_CC, PREF_ACCOUNT);
9442 if (ac->set_autobcc && ac->auto_bcc)
9443 compose_entry_append(compose, ac->auto_bcc,
9444 COMPOSE_BCC, PREF_ACCOUNT);
9446 if (ac->set_autoreplyto && ac->auto_replyto)
9447 compose_entry_append(compose, ac->auto_replyto,
9448 COMPOSE_REPLYTO, PREF_ACCOUNT);
9450 for (list = saved_list; list; list = list->next) {
9451 state = (HeaderEntryState *) list->data;
9453 compose_add_header_entry(compose, state->header,
9454 state->entry, state->type);
9455 if (state->entry_marked)
9456 compose_entry_mark_default_to(compose, state->entry);
9458 g_free(state->header);
9459 g_free(state->entry);
9462 g_slist_free(saved_list);
9464 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9465 (ac->protocol == A_NNTP) ?
9466 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9469 /* Set message save folder */
9470 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9471 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9473 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9474 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9476 compose_set_save_to(compose, NULL);
9477 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9478 folderidentifier = folder_item_get_identifier(account_get_special_folder
9479 (compose->account, F_OUTBOX));
9480 compose_set_save_to(compose, folderidentifier);
9481 g_free(folderidentifier);
9485 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9486 GtkTreeViewColumn *column, Compose *compose)
9488 compose_attach_property(NULL, compose);
9491 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9494 Compose *compose = (Compose *)data;
9495 GtkTreeSelection *attach_selection;
9496 gint attach_nr_selected;
9498 if (!event) return FALSE;
9500 if (event->button == 3) {
9501 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9502 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9504 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9505 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9507 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9508 NULL, NULL, event->button, event->time);
9515 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9518 Compose *compose = (Compose *)data;
9520 if (!event) return FALSE;
9522 switch (event->keyval) {
9523 case GDK_KEY_Delete:
9524 compose_attach_remove_selected(NULL, compose);
9530 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9532 toolbar_comp_set_sensitive(compose, allow);
9533 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9534 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9540 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9542 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9546 static void compose_send_cb(GtkAction *action, gpointer data)
9548 Compose *compose = (Compose *)data;
9550 if (prefs_common.work_offline &&
9551 !inc_offline_should_override(TRUE,
9552 _("Claws Mail needs network access in order "
9553 "to send this email.")))
9556 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9557 g_source_remove(compose->draft_timeout_tag);
9558 compose->draft_timeout_tag = -1;
9561 compose_send(compose);
9564 static void compose_send_later_cb(GtkAction *action, gpointer data)
9566 Compose *compose = (Compose *)data;
9570 compose_allow_user_actions(compose, FALSE);
9571 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9572 compose_allow_user_actions(compose, TRUE);
9576 compose_close(compose);
9577 } else if (val == -1) {
9578 alertpanel_error(_("Could not queue message."));
9579 } else if (val == -2) {
9580 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9581 } else if (val == -3) {
9582 if (privacy_peek_error())
9583 alertpanel_error(_("Could not queue message for sending:\n\n"
9584 "Signature failed: %s"), privacy_get_error());
9585 } else if (val == -4) {
9586 alertpanel_error(_("Could not queue message for sending:\n\n"
9587 "Charset conversion failed."));
9588 } else if (val == -5) {
9589 alertpanel_error(_("Could not queue message for sending:\n\n"
9590 "Couldn't get recipient encryption key."));
9591 } else if (val == -6) {
9594 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9597 #define DRAFTED_AT_EXIT "drafted_at_exit"
9598 static void compose_register_draft(MsgInfo *info)
9600 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9601 DRAFTED_AT_EXIT, NULL);
9602 FILE *fp = g_fopen(filepath, "ab");
9605 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9613 gboolean compose_draft (gpointer data, guint action)
9615 Compose *compose = (Compose *)data;
9620 MsgFlags flag = {0, 0};
9621 static gboolean lock = FALSE;
9622 MsgInfo *newmsginfo;
9624 gboolean target_locked = FALSE;
9625 gboolean err = FALSE;
9627 if (lock) return FALSE;
9629 if (compose->sending)
9632 draft = account_get_special_folder(compose->account, F_DRAFT);
9633 cm_return_val_if_fail(draft != NULL, FALSE);
9635 if (!g_mutex_trylock(compose->mutex)) {
9636 /* we don't want to lock the mutex once it's available,
9637 * because as the only other part of compose.c locking
9638 * it is compose_close - which means once unlocked,
9639 * the compose struct will be freed */
9640 debug_print("couldn't lock mutex, probably sending\n");
9646 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9647 G_DIR_SEPARATOR, compose);
9648 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9649 FILE_OP_ERROR(tmp, "fopen");
9653 /* chmod for security */
9654 if (change_file_mode_rw(fp, tmp) < 0) {
9655 FILE_OP_ERROR(tmp, "chmod");
9656 g_warning("can't change file mode\n");
9659 /* Save draft infos */
9660 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9661 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9663 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9664 gchar *savefolderid;
9666 savefolderid = compose_get_save_to(compose);
9667 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9668 g_free(savefolderid);
9670 if (compose->return_receipt) {
9671 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9673 if (compose->privacy_system) {
9674 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9675 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9676 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9679 /* Message-ID of message replying to */
9680 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9683 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9684 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9687 /* Message-ID of message forwarding to */
9688 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9691 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9692 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9696 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9697 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9699 sheaders = compose_get_manual_headers_info(compose);
9700 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9703 /* end of headers */
9704 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9711 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9715 if (fclose(fp) == EOF) {
9719 if (compose->targetinfo) {
9720 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9721 flag.perm_flags = target_locked?MSG_LOCKED:0;
9723 flag.tmp_flags = MSG_DRAFT;
9725 folder_item_scan(draft);
9726 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9727 MsgInfo *tmpinfo = NULL;
9728 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9729 if (compose->msgid) {
9730 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9733 msgnum = tmpinfo->msgnum;
9734 procmsg_msginfo_free(tmpinfo);
9735 debug_print("got draft msgnum %d from scanning\n", msgnum);
9737 debug_print("didn't get draft msgnum after scanning\n");
9740 debug_print("got draft msgnum %d from adding\n", msgnum);
9746 if (action != COMPOSE_AUTO_SAVE) {
9747 if (action != COMPOSE_DRAFT_FOR_EXIT)
9748 alertpanel_error(_("Could not save draft."));
9751 gtkut_window_popup(compose->window);
9752 val = alertpanel_full(_("Could not save draft"),
9753 _("Could not save draft.\n"
9754 "Do you want to cancel exit or discard this email?"),
9755 _("_Cancel exit"), _("_Discard email"), NULL,
9756 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9757 if (val == G_ALERTALTERNATE) {
9759 g_mutex_unlock(compose->mutex); /* must be done before closing */
9760 compose_close(compose);
9764 g_mutex_unlock(compose->mutex); /* must be done before closing */
9773 if (compose->mode == COMPOSE_REEDIT) {
9774 compose_remove_reedit_target(compose, TRUE);
9777 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9780 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9782 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9784 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9785 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9786 procmsg_msginfo_set_flags(newmsginfo, 0,
9787 MSG_HAS_ATTACHMENT);
9789 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9790 compose_register_draft(newmsginfo);
9792 procmsg_msginfo_free(newmsginfo);
9795 folder_item_scan(draft);
9797 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9799 g_mutex_unlock(compose->mutex); /* must be done before closing */
9800 compose_close(compose);
9806 path = folder_item_fetch_msg(draft, msgnum);
9808 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9811 if (g_stat(path, &s) < 0) {
9812 FILE_OP_ERROR(path, "stat");
9818 procmsg_msginfo_free(compose->targetinfo);
9819 compose->targetinfo = procmsg_msginfo_new();
9820 compose->targetinfo->msgnum = msgnum;
9821 compose->targetinfo->size = (goffset)s.st_size;
9822 compose->targetinfo->mtime = s.st_mtime;
9823 compose->targetinfo->folder = draft;
9825 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9826 compose->mode = COMPOSE_REEDIT;
9828 if (action == COMPOSE_AUTO_SAVE) {
9829 compose->autosaved_draft = compose->targetinfo;
9831 compose->modified = FALSE;
9832 compose_set_title(compose);
9836 g_mutex_unlock(compose->mutex);
9840 void compose_clear_exit_drafts(void)
9842 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9843 DRAFTED_AT_EXIT, NULL);
9844 if (is_file_exist(filepath))
9845 claws_unlink(filepath);
9850 void compose_reopen_exit_drafts(void)
9852 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9853 DRAFTED_AT_EXIT, NULL);
9854 FILE *fp = g_fopen(filepath, "rb");
9858 while (fgets(buf, sizeof(buf), fp)) {
9859 gchar **parts = g_strsplit(buf, "\t", 2);
9860 const gchar *folder = parts[0];
9861 int msgnum = parts[1] ? atoi(parts[1]):-1;
9863 if (folder && *folder && msgnum > -1) {
9864 FolderItem *item = folder_find_item_from_identifier(folder);
9865 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9867 compose_reedit(info, FALSE);
9874 compose_clear_exit_drafts();
9877 static void compose_save_cb(GtkAction *action, gpointer data)
9879 Compose *compose = (Compose *)data;
9880 compose_draft(compose, COMPOSE_KEEP_EDITING);
9881 compose->rmode = COMPOSE_REEDIT;
9884 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9886 if (compose && file_list) {
9889 for ( tmp = file_list; tmp; tmp = tmp->next) {
9890 gchar *file = (gchar *) tmp->data;
9891 gchar *utf8_filename = conv_filename_to_utf8(file);
9892 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9893 compose_changed_cb(NULL, compose);
9898 g_free(utf8_filename);
9903 static void compose_attach_cb(GtkAction *action, gpointer data)
9905 Compose *compose = (Compose *)data;
9908 if (compose->redirect_filename != NULL)
9911 /* Set focus_window properly, in case we were called via popup menu,
9912 * which unsets it (via focus_out_event callback on compose window). */
9913 manage_window_focus_in(compose->window, NULL, NULL);
9915 file_list = filesel_select_multiple_files_open(_("Select file"));
9918 compose_attach_from_list(compose, file_list, TRUE);
9919 g_list_free(file_list);
9923 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9925 Compose *compose = (Compose *)data;
9927 gint files_inserted = 0;
9929 file_list = filesel_select_multiple_files_open(_("Select file"));
9934 for ( tmp = file_list; tmp; tmp = tmp->next) {
9935 gchar *file = (gchar *) tmp->data;
9936 gchar *filedup = g_strdup(file);
9937 gchar *shortfile = g_path_get_basename(filedup);
9938 ComposeInsertResult res;
9939 /* insert the file if the file is short or if the user confirmed that
9940 he/she wants to insert the large file */
9941 res = compose_insert_file(compose, file);
9942 if (res == COMPOSE_INSERT_READ_ERROR) {
9943 alertpanel_error(_("File '%s' could not be read."), shortfile);
9944 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9945 alertpanel_error(_("File '%s' contained invalid characters\n"
9946 "for the current encoding, insertion may be incorrect."),
9948 } else if (res == COMPOSE_INSERT_SUCCESS)
9955 g_list_free(file_list);
9959 if (files_inserted > 0 && compose->gtkaspell &&
9960 compose->gtkaspell->check_while_typing)
9961 gtkaspell_highlight_all(compose->gtkaspell);
9965 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9967 Compose *compose = (Compose *)data;
9969 compose_insert_sig(compose, FALSE);
9972 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9976 Compose *compose = (Compose *)data;
9978 gtkut_widget_get_uposition(widget, &x, &y);
9979 if (!compose->batch) {
9980 prefs_common.compose_x = x;
9981 prefs_common.compose_y = y;
9983 if (compose->sending || compose->updating)
9985 compose_close_cb(NULL, compose);
9989 void compose_close_toolbar(Compose *compose)
9991 compose_close_cb(NULL, compose);
9994 static void compose_close_cb(GtkAction *action, gpointer data)
9996 Compose *compose = (Compose *)data;
10000 if (compose->exteditor_tag != -1) {
10001 if (!compose_ext_editor_kill(compose))
10006 if (compose->modified) {
10007 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10008 if (!g_mutex_trylock(compose->mutex)) {
10009 /* we don't want to lock the mutex once it's available,
10010 * because as the only other part of compose.c locking
10011 * it is compose_close - which means once unlocked,
10012 * the compose struct will be freed */
10013 debug_print("couldn't lock mutex, probably sending\n");
10017 val = alertpanel(_("Discard message"),
10018 _("This message has been modified. Discard it?"),
10019 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10021 val = alertpanel(_("Save changes"),
10022 _("This message has been modified. Save the latest changes?"),
10023 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10025 g_mutex_unlock(compose->mutex);
10027 case G_ALERTDEFAULT:
10028 if (prefs_common.autosave && !reedit)
10029 compose_remove_draft(compose);
10031 case G_ALERTALTERNATE:
10032 compose_draft(data, COMPOSE_QUIT_EDITING);
10039 compose_close(compose);
10042 static void compose_print_cb(GtkAction *action, gpointer data)
10044 Compose *compose = (Compose *) data;
10046 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10047 if (compose->targetinfo)
10048 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10051 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10053 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10054 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10055 Compose *compose = (Compose *) data;
10058 compose->out_encoding = (CharSet)value;
10061 static void compose_address_cb(GtkAction *action, gpointer data)
10063 Compose *compose = (Compose *)data;
10065 #ifndef USE_NEW_ADDRBOOK
10066 addressbook_open(compose);
10068 GError* error = NULL;
10069 addressbook_connect_signals(compose);
10070 addressbook_dbus_open(TRUE, &error);
10072 g_warning("%s", error->message);
10073 g_error_free(error);
10078 static void about_show_cb(GtkAction *action, gpointer data)
10083 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10085 Compose *compose = (Compose *)data;
10090 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10091 cm_return_if_fail(tmpl != NULL);
10093 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10095 val = alertpanel(_("Apply template"), msg,
10096 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10099 if (val == G_ALERTDEFAULT)
10100 compose_template_apply(compose, tmpl, TRUE);
10101 else if (val == G_ALERTALTERNATE)
10102 compose_template_apply(compose, tmpl, FALSE);
10105 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10107 Compose *compose = (Compose *)data;
10109 compose_exec_ext_editor(compose);
10112 static void compose_undo_cb(GtkAction *action, gpointer data)
10114 Compose *compose = (Compose *)data;
10115 gboolean prev_autowrap = compose->autowrap;
10117 compose->autowrap = FALSE;
10118 undo_undo(compose->undostruct);
10119 compose->autowrap = prev_autowrap;
10122 static void compose_redo_cb(GtkAction *action, gpointer data)
10124 Compose *compose = (Compose *)data;
10125 gboolean prev_autowrap = compose->autowrap;
10127 compose->autowrap = FALSE;
10128 undo_redo(compose->undostruct);
10129 compose->autowrap = prev_autowrap;
10132 static void entry_cut_clipboard(GtkWidget *entry)
10134 if (GTK_IS_EDITABLE(entry))
10135 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10136 else if (GTK_IS_TEXT_VIEW(entry))
10137 gtk_text_buffer_cut_clipboard(
10138 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10139 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10143 static void entry_copy_clipboard(GtkWidget *entry)
10145 if (GTK_IS_EDITABLE(entry))
10146 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10147 else if (GTK_IS_TEXT_VIEW(entry))
10148 gtk_text_buffer_copy_clipboard(
10149 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10150 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10153 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10154 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10156 if (GTK_IS_TEXT_VIEW(entry)) {
10157 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10158 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10159 GtkTextIter start_iter, end_iter;
10161 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10163 if (contents == NULL)
10166 /* we shouldn't delete the selection when middle-click-pasting, or we
10167 * can't mid-click-paste our own selection */
10168 if (clip != GDK_SELECTION_PRIMARY) {
10169 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10170 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10173 if (insert_place == NULL) {
10174 /* if insert_place isn't specified, insert at the cursor.
10175 * used for Ctrl-V pasting */
10176 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10177 start = gtk_text_iter_get_offset(&start_iter);
10178 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10180 /* if insert_place is specified, paste here.
10181 * used for mid-click-pasting */
10182 start = gtk_text_iter_get_offset(insert_place);
10183 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10184 if (prefs_common.primary_paste_unselects)
10185 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10189 /* paste unwrapped: mark the paste so it's not wrapped later */
10190 end = start + strlen(contents);
10191 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10192 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10193 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10194 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10195 /* rewrap paragraph now (after a mid-click-paste) */
10196 mark_start = gtk_text_buffer_get_insert(buffer);
10197 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10198 gtk_text_iter_backward_char(&start_iter);
10199 compose_beautify_paragraph(compose, &start_iter, TRUE);
10201 } else if (GTK_IS_EDITABLE(entry))
10202 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10204 compose->modified = TRUE;
10207 static void entry_allsel(GtkWidget *entry)
10209 if (GTK_IS_EDITABLE(entry))
10210 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10211 else if (GTK_IS_TEXT_VIEW(entry)) {
10212 GtkTextIter startiter, enditer;
10213 GtkTextBuffer *textbuf;
10215 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10216 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10217 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10219 gtk_text_buffer_move_mark_by_name(textbuf,
10220 "selection_bound", &startiter);
10221 gtk_text_buffer_move_mark_by_name(textbuf,
10222 "insert", &enditer);
10226 static void compose_cut_cb(GtkAction *action, gpointer data)
10228 Compose *compose = (Compose *)data;
10229 if (compose->focused_editable
10230 #ifndef GENERIC_UMPC
10231 && gtk_widget_has_focus(compose->focused_editable)
10234 entry_cut_clipboard(compose->focused_editable);
10237 static void compose_copy_cb(GtkAction *action, gpointer data)
10239 Compose *compose = (Compose *)data;
10240 if (compose->focused_editable
10241 #ifndef GENERIC_UMPC
10242 && gtk_widget_has_focus(compose->focused_editable)
10245 entry_copy_clipboard(compose->focused_editable);
10248 static void compose_paste_cb(GtkAction *action, gpointer data)
10250 Compose *compose = (Compose *)data;
10251 gint prev_autowrap;
10252 GtkTextBuffer *buffer;
10254 if (compose->focused_editable &&
10255 #ifndef GENERIC_UMPC
10256 gtk_widget_has_focus(compose->focused_editable)
10259 entry_paste_clipboard(compose, compose->focused_editable,
10260 prefs_common.linewrap_pastes,
10261 GDK_SELECTION_CLIPBOARD, NULL);
10266 #ifndef GENERIC_UMPC
10267 gtk_widget_has_focus(compose->text) &&
10269 compose->gtkaspell &&
10270 compose->gtkaspell->check_while_typing)
10271 gtkaspell_highlight_all(compose->gtkaspell);
10275 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10277 Compose *compose = (Compose *)data;
10278 gint wrap_quote = prefs_common.linewrap_quote;
10279 if (compose->focused_editable
10280 #ifndef GENERIC_UMPC
10281 && gtk_widget_has_focus(compose->focused_editable)
10284 /* let text_insert() (called directly or at a later time
10285 * after the gtk_editable_paste_clipboard) know that
10286 * text is to be inserted as a quotation. implemented
10287 * by using a simple refcount... */
10288 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10289 G_OBJECT(compose->focused_editable),
10290 "paste_as_quotation"));
10291 g_object_set_data(G_OBJECT(compose->focused_editable),
10292 "paste_as_quotation",
10293 GINT_TO_POINTER(paste_as_quotation + 1));
10294 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10295 entry_paste_clipboard(compose, compose->focused_editable,
10296 prefs_common.linewrap_pastes,
10297 GDK_SELECTION_CLIPBOARD, NULL);
10298 prefs_common.linewrap_quote = wrap_quote;
10302 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10304 Compose *compose = (Compose *)data;
10305 gint prev_autowrap;
10306 GtkTextBuffer *buffer;
10308 if (compose->focused_editable
10309 #ifndef GENERIC_UMPC
10310 && gtk_widget_has_focus(compose->focused_editable)
10313 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10314 GDK_SELECTION_CLIPBOARD, NULL);
10319 #ifndef GENERIC_UMPC
10320 gtk_widget_has_focus(compose->text) &&
10322 compose->gtkaspell &&
10323 compose->gtkaspell->check_while_typing)
10324 gtkaspell_highlight_all(compose->gtkaspell);
10328 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10330 Compose *compose = (Compose *)data;
10331 gint prev_autowrap;
10332 GtkTextBuffer *buffer;
10334 if (compose->focused_editable
10335 #ifndef GENERIC_UMPC
10336 && gtk_widget_has_focus(compose->focused_editable)
10339 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10340 GDK_SELECTION_CLIPBOARD, NULL);
10345 #ifndef GENERIC_UMPC
10346 gtk_widget_has_focus(compose->text) &&
10348 compose->gtkaspell &&
10349 compose->gtkaspell->check_while_typing)
10350 gtkaspell_highlight_all(compose->gtkaspell);
10354 static void compose_allsel_cb(GtkAction *action, gpointer data)
10356 Compose *compose = (Compose *)data;
10357 if (compose->focused_editable
10358 #ifndef GENERIC_UMPC
10359 && gtk_widget_has_focus(compose->focused_editable)
10362 entry_allsel(compose->focused_editable);
10365 static void textview_move_beginning_of_line (GtkTextView *text)
10367 GtkTextBuffer *buffer;
10371 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10373 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10374 mark = gtk_text_buffer_get_insert(buffer);
10375 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10376 gtk_text_iter_set_line_offset(&ins, 0);
10377 gtk_text_buffer_place_cursor(buffer, &ins);
10380 static void textview_move_forward_character (GtkTextView *text)
10382 GtkTextBuffer *buffer;
10386 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10388 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10389 mark = gtk_text_buffer_get_insert(buffer);
10390 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10391 if (gtk_text_iter_forward_cursor_position(&ins))
10392 gtk_text_buffer_place_cursor(buffer, &ins);
10395 static void textview_move_backward_character (GtkTextView *text)
10397 GtkTextBuffer *buffer;
10401 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10403 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10404 mark = gtk_text_buffer_get_insert(buffer);
10405 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10406 if (gtk_text_iter_backward_cursor_position(&ins))
10407 gtk_text_buffer_place_cursor(buffer, &ins);
10410 static void textview_move_forward_word (GtkTextView *text)
10412 GtkTextBuffer *buffer;
10417 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10419 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10420 mark = gtk_text_buffer_get_insert(buffer);
10421 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10422 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10423 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10424 gtk_text_iter_backward_word_start(&ins);
10425 gtk_text_buffer_place_cursor(buffer, &ins);
10429 static void textview_move_backward_word (GtkTextView *text)
10431 GtkTextBuffer *buffer;
10435 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10437 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10438 mark = gtk_text_buffer_get_insert(buffer);
10439 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10440 if (gtk_text_iter_backward_word_starts(&ins, 1))
10441 gtk_text_buffer_place_cursor(buffer, &ins);
10444 static void textview_move_end_of_line (GtkTextView *text)
10446 GtkTextBuffer *buffer;
10450 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10452 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10453 mark = gtk_text_buffer_get_insert(buffer);
10454 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10455 if (gtk_text_iter_forward_to_line_end(&ins))
10456 gtk_text_buffer_place_cursor(buffer, &ins);
10459 static void textview_move_next_line (GtkTextView *text)
10461 GtkTextBuffer *buffer;
10466 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10468 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10469 mark = gtk_text_buffer_get_insert(buffer);
10470 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10471 offset = gtk_text_iter_get_line_offset(&ins);
10472 if (gtk_text_iter_forward_line(&ins)) {
10473 gtk_text_iter_set_line_offset(&ins, offset);
10474 gtk_text_buffer_place_cursor(buffer, &ins);
10478 static void textview_move_previous_line (GtkTextView *text)
10480 GtkTextBuffer *buffer;
10485 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10487 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10488 mark = gtk_text_buffer_get_insert(buffer);
10489 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10490 offset = gtk_text_iter_get_line_offset(&ins);
10491 if (gtk_text_iter_backward_line(&ins)) {
10492 gtk_text_iter_set_line_offset(&ins, offset);
10493 gtk_text_buffer_place_cursor(buffer, &ins);
10497 static void textview_delete_forward_character (GtkTextView *text)
10499 GtkTextBuffer *buffer;
10501 GtkTextIter ins, end_iter;
10503 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10505 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10506 mark = gtk_text_buffer_get_insert(buffer);
10507 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10509 if (gtk_text_iter_forward_char(&end_iter)) {
10510 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10514 static void textview_delete_backward_character (GtkTextView *text)
10516 GtkTextBuffer *buffer;
10518 GtkTextIter ins, end_iter;
10520 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10522 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10523 mark = gtk_text_buffer_get_insert(buffer);
10524 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10526 if (gtk_text_iter_backward_char(&end_iter)) {
10527 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10531 static void textview_delete_forward_word (GtkTextView *text)
10533 GtkTextBuffer *buffer;
10535 GtkTextIter ins, end_iter;
10537 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10539 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10540 mark = gtk_text_buffer_get_insert(buffer);
10541 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10543 if (gtk_text_iter_forward_word_end(&end_iter)) {
10544 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10548 static void textview_delete_backward_word (GtkTextView *text)
10550 GtkTextBuffer *buffer;
10552 GtkTextIter ins, end_iter;
10554 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10556 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10557 mark = gtk_text_buffer_get_insert(buffer);
10558 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10560 if (gtk_text_iter_backward_word_start(&end_iter)) {
10561 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10565 static void textview_delete_line (GtkTextView *text)
10567 GtkTextBuffer *buffer;
10569 GtkTextIter ins, start_iter, end_iter;
10571 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10573 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10574 mark = gtk_text_buffer_get_insert(buffer);
10575 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10578 gtk_text_iter_set_line_offset(&start_iter, 0);
10581 if (gtk_text_iter_ends_line(&end_iter)){
10582 if (!gtk_text_iter_forward_char(&end_iter))
10583 gtk_text_iter_backward_char(&start_iter);
10586 gtk_text_iter_forward_to_line_end(&end_iter);
10587 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10590 static void textview_delete_to_line_end (GtkTextView *text)
10592 GtkTextBuffer *buffer;
10594 GtkTextIter ins, end_iter;
10596 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10598 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10599 mark = gtk_text_buffer_get_insert(buffer);
10600 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10602 if (gtk_text_iter_ends_line(&end_iter))
10603 gtk_text_iter_forward_char(&end_iter);
10605 gtk_text_iter_forward_to_line_end(&end_iter);
10606 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10609 #define DO_ACTION(name, act) { \
10610 if(!strcmp(name, a_name)) { \
10614 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10616 const gchar *a_name = gtk_action_get_name(action);
10617 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10618 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10619 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10620 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10621 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10622 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10623 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10624 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10625 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10626 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10627 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10628 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10629 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10630 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10634 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10636 Compose *compose = (Compose *)data;
10637 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10638 ComposeCallAdvancedAction action = -1;
10640 action = compose_call_advanced_action_from_path(gaction);
10643 void (*do_action) (GtkTextView *text);
10644 } action_table[] = {
10645 {textview_move_beginning_of_line},
10646 {textview_move_forward_character},
10647 {textview_move_backward_character},
10648 {textview_move_forward_word},
10649 {textview_move_backward_word},
10650 {textview_move_end_of_line},
10651 {textview_move_next_line},
10652 {textview_move_previous_line},
10653 {textview_delete_forward_character},
10654 {textview_delete_backward_character},
10655 {textview_delete_forward_word},
10656 {textview_delete_backward_word},
10657 {textview_delete_line},
10658 {textview_delete_to_line_end}
10661 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10663 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10664 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10665 if (action_table[action].do_action)
10666 action_table[action].do_action(text);
10668 g_warning("Not implemented yet.");
10672 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10674 GtkAllocation allocation;
10678 if (GTK_IS_EDITABLE(widget)) {
10679 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10680 gtk_editable_set_position(GTK_EDITABLE(widget),
10683 if ((parent = gtk_widget_get_parent(widget))
10684 && (parent = gtk_widget_get_parent(parent))
10685 && (parent = gtk_widget_get_parent(parent))) {
10686 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10687 gtk_widget_get_allocation(widget, &allocation);
10688 gint y = allocation.y;
10689 gint height = allocation.height;
10690 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10691 (GTK_SCROLLED_WINDOW(parent));
10693 gfloat value = gtk_adjustment_get_value(shown);
10694 gfloat upper = gtk_adjustment_get_upper(shown);
10695 gfloat page_size = gtk_adjustment_get_page_size(shown);
10696 if (y < (int)value) {
10697 gtk_adjustment_set_value(shown, y - 1);
10699 if ((y + height) > ((int)value + (int)page_size)) {
10700 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10701 gtk_adjustment_set_value(shown,
10702 y + height - (int)page_size - 1);
10704 gtk_adjustment_set_value(shown,
10705 (int)upper - (int)page_size - 1);
10712 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10713 compose->focused_editable = widget;
10715 #ifdef GENERIC_UMPC
10716 if (GTK_IS_TEXT_VIEW(widget)
10717 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10718 g_object_ref(compose->notebook);
10719 g_object_ref(compose->edit_vbox);
10720 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10721 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10722 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10723 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10724 g_object_unref(compose->notebook);
10725 g_object_unref(compose->edit_vbox);
10726 g_signal_handlers_block_by_func(G_OBJECT(widget),
10727 G_CALLBACK(compose_grab_focus_cb),
10729 gtk_widget_grab_focus(widget);
10730 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10731 G_CALLBACK(compose_grab_focus_cb),
10733 } else if (!GTK_IS_TEXT_VIEW(widget)
10734 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10735 g_object_ref(compose->notebook);
10736 g_object_ref(compose->edit_vbox);
10737 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10738 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10739 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10740 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10741 g_object_unref(compose->notebook);
10742 g_object_unref(compose->edit_vbox);
10743 g_signal_handlers_block_by_func(G_OBJECT(widget),
10744 G_CALLBACK(compose_grab_focus_cb),
10746 gtk_widget_grab_focus(widget);
10747 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10748 G_CALLBACK(compose_grab_focus_cb),
10754 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10756 compose->modified = TRUE;
10757 // compose_beautify_paragraph(compose, NULL, TRUE);
10758 #ifndef GENERIC_UMPC
10759 compose_set_title(compose);
10763 static void compose_wrap_cb(GtkAction *action, gpointer data)
10765 Compose *compose = (Compose *)data;
10766 compose_beautify_paragraph(compose, NULL, TRUE);
10769 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10771 Compose *compose = (Compose *)data;
10772 compose_wrap_all_full(compose, TRUE);
10775 static void compose_find_cb(GtkAction *action, gpointer data)
10777 Compose *compose = (Compose *)data;
10779 message_search_compose(compose);
10782 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10785 Compose *compose = (Compose *)data;
10786 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10787 if (compose->autowrap)
10788 compose_wrap_all_full(compose, TRUE);
10789 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10792 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10795 Compose *compose = (Compose *)data;
10796 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10799 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10801 Compose *compose = (Compose *)data;
10803 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10806 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10808 Compose *compose = (Compose *)data;
10810 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10813 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10815 g_free(compose->privacy_system);
10817 compose->privacy_system = g_strdup(account->default_privacy_system);
10818 compose_update_privacy_system_menu_item(compose, warn);
10821 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10823 Compose *compose = (Compose *)data;
10825 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10826 gtk_widget_show(compose->ruler_hbox);
10827 prefs_common.show_ruler = TRUE;
10829 gtk_widget_hide(compose->ruler_hbox);
10830 gtk_widget_queue_resize(compose->edit_vbox);
10831 prefs_common.show_ruler = FALSE;
10835 static void compose_attach_drag_received_cb (GtkWidget *widget,
10836 GdkDragContext *context,
10839 GtkSelectionData *data,
10842 gpointer user_data)
10844 Compose *compose = (Compose *)user_data;
10848 type = gtk_selection_data_get_data_type(data);
10849 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10851 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10853 ) && gtk_drag_get_source_widget(context) !=
10854 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10855 list = uri_list_extract_filenames(
10856 (const gchar *)gtk_selection_data_get_data(data));
10857 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10858 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10859 compose_attach_append
10860 (compose, (const gchar *)tmp->data,
10861 utf8_filename, NULL, NULL);
10862 g_free(utf8_filename);
10864 if (list) compose_changed_cb(NULL, compose);
10865 list_free_strings(list);
10867 } else if (gtk_drag_get_source_widget(context)
10868 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10869 /* comes from our summaryview */
10870 SummaryView * summaryview = NULL;
10871 GSList * list = NULL, *cur = NULL;
10873 if (mainwindow_get_mainwindow())
10874 summaryview = mainwindow_get_mainwindow()->summaryview;
10877 list = summary_get_selected_msg_list(summaryview);
10879 for (cur = list; cur; cur = cur->next) {
10880 MsgInfo *msginfo = (MsgInfo *)cur->data;
10881 gchar *file = NULL;
10883 file = procmsg_get_message_file_full(msginfo,
10886 compose_attach_append(compose, (const gchar *)file,
10887 (const gchar *)file, "message/rfc822", NULL);
10891 g_slist_free(list);
10895 static gboolean compose_drag_drop(GtkWidget *widget,
10896 GdkDragContext *drag_context,
10898 guint time, gpointer user_data)
10900 /* not handling this signal makes compose_insert_drag_received_cb
10905 static gboolean completion_set_focus_to_subject
10906 (GtkWidget *widget,
10907 GdkEventKey *event,
10910 cm_return_val_if_fail(compose != NULL, FALSE);
10912 /* make backtab move to subject field */
10913 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10914 gtk_widget_grab_focus(compose->subject_entry);
10920 static void compose_insert_drag_received_cb (GtkWidget *widget,
10921 GdkDragContext *drag_context,
10924 GtkSelectionData *data,
10927 gpointer user_data)
10929 Compose *compose = (Compose *)user_data;
10933 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10935 type = gtk_selection_data_get_data_type(data);
10937 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10939 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10941 AlertValue val = G_ALERTDEFAULT;
10942 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10944 list = uri_list_extract_filenames(ddata);
10945 if (list == NULL && strstr(ddata, "://")) {
10946 /* Assume a list of no files, and data has ://, is a remote link */
10947 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10948 gchar *tmpfile = get_tmp_file();
10949 str_write_to_file(tmpdata, tmpfile);
10951 compose_insert_file(compose, tmpfile);
10952 claws_unlink(tmpfile);
10954 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10955 compose_beautify_paragraph(compose, NULL, TRUE);
10958 switch (prefs_common.compose_dnd_mode) {
10959 case COMPOSE_DND_ASK:
10960 val = alertpanel_full(_("Insert or attach?"),
10961 _("Do you want to insert the contents of the file(s) "
10962 "into the message body, or attach it to the email?"),
10963 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10964 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10966 case COMPOSE_DND_INSERT:
10967 val = G_ALERTALTERNATE;
10969 case COMPOSE_DND_ATTACH:
10970 val = G_ALERTOTHER;
10973 /* unexpected case */
10974 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10977 if (val & G_ALERTDISABLE) {
10978 val &= ~G_ALERTDISABLE;
10979 /* remember what action to perform by default, only if we don't click Cancel */
10980 if (val == G_ALERTALTERNATE)
10981 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10982 else if (val == G_ALERTOTHER)
10983 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10986 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10987 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10988 list_free_strings(list);
10991 } else if (val == G_ALERTOTHER) {
10992 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10993 list_free_strings(list);
10998 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10999 compose_insert_file(compose, (const gchar *)tmp->data);
11001 list_free_strings(list);
11003 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11008 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11011 static void compose_header_drag_received_cb (GtkWidget *widget,
11012 GdkDragContext *drag_context,
11015 GtkSelectionData *data,
11018 gpointer user_data)
11020 GtkEditable *entry = (GtkEditable *)user_data;
11021 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11023 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11026 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11027 gchar *decoded=g_new(gchar, strlen(email));
11030 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11031 gtk_editable_delete_text(entry, 0, -1);
11032 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11033 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11037 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11040 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11042 Compose *compose = (Compose *)data;
11044 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11045 compose->return_receipt = TRUE;
11047 compose->return_receipt = FALSE;
11050 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11052 Compose *compose = (Compose *)data;
11054 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11055 compose->remove_references = TRUE;
11057 compose->remove_references = FALSE;
11060 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11061 ComposeHeaderEntry *headerentry)
11063 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11067 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11068 GdkEventKey *event,
11069 ComposeHeaderEntry *headerentry)
11071 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11072 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11073 !(event->state & GDK_MODIFIER_MASK) &&
11074 (event->keyval == GDK_KEY_BackSpace) &&
11075 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11076 gtk_container_remove
11077 (GTK_CONTAINER(headerentry->compose->header_table),
11078 headerentry->combo);
11079 gtk_container_remove
11080 (GTK_CONTAINER(headerentry->compose->header_table),
11081 headerentry->entry);
11082 headerentry->compose->header_list =
11083 g_slist_remove(headerentry->compose->header_list,
11085 g_free(headerentry);
11086 } else if (event->keyval == GDK_KEY_Tab) {
11087 if (headerentry->compose->header_last == headerentry) {
11088 /* Override default next focus, and give it to subject_entry
11089 * instead of notebook tabs
11091 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11092 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11099 static gboolean scroll_postpone(gpointer data)
11101 Compose *compose = (Compose *)data;
11103 cm_return_val_if_fail(!compose->batch, FALSE);
11105 GTK_EVENTS_FLUSH();
11106 compose_show_first_last_header(compose, FALSE);
11110 static void compose_headerentry_changed_cb(GtkWidget *entry,
11111 ComposeHeaderEntry *headerentry)
11113 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11114 compose_create_header_entry(headerentry->compose);
11115 g_signal_handlers_disconnect_matched
11116 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11117 0, 0, NULL, NULL, headerentry);
11119 if (!headerentry->compose->batch)
11120 g_timeout_add(0, scroll_postpone, headerentry->compose);
11124 static gboolean compose_defer_auto_save_draft(Compose *compose)
11126 compose->draft_timeout_tag = -1;
11127 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11131 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11133 GtkAdjustment *vadj;
11135 cm_return_if_fail(compose);
11136 cm_return_if_fail(!compose->batch);
11137 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11138 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11139 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11140 gtk_widget_get_parent(compose->header_table)));
11141 gtk_adjustment_set_value(vadj, (show_first ?
11142 gtk_adjustment_get_lower(vadj) :
11143 (gtk_adjustment_get_upper(vadj) -
11144 gtk_adjustment_get_page_size(vadj))));
11145 gtk_adjustment_changed(vadj);
11148 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11149 const gchar *text, gint len, Compose *compose)
11151 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11152 (G_OBJECT(compose->text), "paste_as_quotation"));
11155 cm_return_if_fail(text != NULL);
11157 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11158 G_CALLBACK(text_inserted),
11160 if (paste_as_quotation) {
11162 const gchar *qmark;
11164 GtkTextIter start_iter;
11167 len = strlen(text);
11169 new_text = g_strndup(text, len);
11171 qmark = compose_quote_char_from_context(compose);
11173 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11174 gtk_text_buffer_place_cursor(buffer, iter);
11176 pos = gtk_text_iter_get_offset(iter);
11178 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11179 _("Quote format error at line %d."));
11180 quote_fmt_reset_vartable();
11182 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11183 GINT_TO_POINTER(paste_as_quotation - 1));
11185 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11186 gtk_text_buffer_place_cursor(buffer, iter);
11187 gtk_text_buffer_delete_mark(buffer, mark);
11189 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11190 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11191 compose_beautify_paragraph(compose, &start_iter, FALSE);
11192 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11193 gtk_text_buffer_delete_mark(buffer, mark);
11195 if (strcmp(text, "\n") || compose->automatic_break
11196 || gtk_text_iter_starts_line(iter)) {
11197 GtkTextIter before_ins;
11198 gtk_text_buffer_insert(buffer, iter, text, len);
11199 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11200 before_ins = *iter;
11201 gtk_text_iter_backward_chars(&before_ins, len);
11202 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11205 /* check if the preceding is just whitespace or quote */
11206 GtkTextIter start_line;
11207 gchar *tmp = NULL, *quote = NULL;
11208 gint quote_len = 0, is_normal = 0;
11209 start_line = *iter;
11210 gtk_text_iter_set_line_offset(&start_line, 0);
11211 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11214 if (*tmp == '\0') {
11217 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11225 gtk_text_buffer_insert(buffer, iter, text, len);
11227 gtk_text_buffer_insert_with_tags_by_name(buffer,
11228 iter, text, len, "no_join", NULL);
11233 if (!paste_as_quotation) {
11234 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11235 compose_beautify_paragraph(compose, iter, FALSE);
11236 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11237 gtk_text_buffer_delete_mark(buffer, mark);
11240 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11241 G_CALLBACK(text_inserted),
11243 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11245 if (prefs_common.autosave &&
11246 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11247 compose->draft_timeout_tag != -2 /* disabled while loading */)
11248 compose->draft_timeout_tag = g_timeout_add
11249 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11253 static void compose_check_all(GtkAction *action, gpointer data)
11255 Compose *compose = (Compose *)data;
11256 if (!compose->gtkaspell)
11259 if (gtk_widget_has_focus(compose->subject_entry))
11260 claws_spell_entry_check_all(
11261 CLAWS_SPELL_ENTRY(compose->subject_entry));
11263 gtkaspell_check_all(compose->gtkaspell);
11266 static void compose_highlight_all(GtkAction *action, gpointer data)
11268 Compose *compose = (Compose *)data;
11269 if (compose->gtkaspell) {
11270 claws_spell_entry_recheck_all(
11271 CLAWS_SPELL_ENTRY(compose->subject_entry));
11272 gtkaspell_highlight_all(compose->gtkaspell);
11276 static void compose_check_backwards(GtkAction *action, gpointer data)
11278 Compose *compose = (Compose *)data;
11279 if (!compose->gtkaspell) {
11280 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11284 if (gtk_widget_has_focus(compose->subject_entry))
11285 claws_spell_entry_check_backwards(
11286 CLAWS_SPELL_ENTRY(compose->subject_entry));
11288 gtkaspell_check_backwards(compose->gtkaspell);
11291 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11293 Compose *compose = (Compose *)data;
11294 if (!compose->gtkaspell) {
11295 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11299 if (gtk_widget_has_focus(compose->subject_entry))
11300 claws_spell_entry_check_forwards_go(
11301 CLAWS_SPELL_ENTRY(compose->subject_entry));
11303 gtkaspell_check_forwards_go(compose->gtkaspell);
11308 *\brief Guess originating forward account from MsgInfo and several
11309 * "common preference" settings. Return NULL if no guess.
11311 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11313 PrefsAccount *account = NULL;
11315 cm_return_val_if_fail(msginfo, NULL);
11316 cm_return_val_if_fail(msginfo->folder, NULL);
11317 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11319 if (msginfo->folder->prefs->enable_default_account)
11320 account = account_find_from_id(msginfo->folder->prefs->default_account);
11323 account = msginfo->folder->folder->account;
11325 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11327 Xstrdup_a(to, msginfo->to, return NULL);
11328 extract_address(to);
11329 account = account_find_from_address(to, FALSE);
11332 if (!account && prefs_common.forward_account_autosel) {
11333 gchar cc[BUFFSIZE];
11334 if (!procheader_get_header_from_msginfo
11335 (msginfo, cc,sizeof cc , "Cc:")) {
11336 gchar *buf = cc + strlen("Cc:");
11337 extract_address(buf);
11338 account = account_find_from_address(buf, FALSE);
11342 if (!account && prefs_common.forward_account_autosel) {
11343 gchar deliveredto[BUFFSIZE];
11344 if (!procheader_get_header_from_msginfo
11345 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11346 gchar *buf = deliveredto + strlen("Delivered-To:");
11347 extract_address(buf);
11348 account = account_find_from_address(buf, FALSE);
11355 gboolean compose_close(Compose *compose)
11359 if (!g_mutex_trylock(compose->mutex)) {
11360 /* we have to wait for the (possibly deferred by auto-save)
11361 * drafting to be done, before destroying the compose under
11363 debug_print("waiting for drafting to finish...\n");
11364 compose_allow_user_actions(compose, FALSE);
11365 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11368 cm_return_val_if_fail(compose, FALSE);
11369 gtkut_widget_get_uposition(compose->window, &x, &y);
11370 if (!compose->batch) {
11371 prefs_common.compose_x = x;
11372 prefs_common.compose_y = y;
11374 g_mutex_unlock(compose->mutex);
11375 compose_destroy(compose);
11380 * Add entry field for each address in list.
11381 * \param compose E-Mail composition object.
11382 * \param listAddress List of (formatted) E-Mail addresses.
11384 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11387 node = listAddress;
11389 addr = ( gchar * ) node->data;
11390 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11391 node = g_list_next( node );
11395 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11396 guint action, gboolean opening_multiple)
11398 gchar *body = NULL;
11399 GSList *new_msglist = NULL;
11400 MsgInfo *tmp_msginfo = NULL;
11401 gboolean originally_enc = FALSE;
11402 gboolean originally_sig = FALSE;
11403 Compose *compose = NULL;
11404 gchar *s_system = NULL;
11406 cm_return_if_fail(msgview != NULL);
11408 cm_return_if_fail(msginfo_list != NULL);
11410 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11411 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11412 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11414 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11415 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11416 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11417 orig_msginfo, mimeinfo);
11418 if (tmp_msginfo != NULL) {
11419 new_msglist = g_slist_append(NULL, tmp_msginfo);
11421 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11422 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11423 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11425 tmp_msginfo->folder = orig_msginfo->folder;
11426 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11427 if (orig_msginfo->tags) {
11428 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11429 tmp_msginfo->folder->tags_dirty = TRUE;
11435 if (!opening_multiple)
11436 body = messageview_get_selection(msgview);
11439 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11440 procmsg_msginfo_free(tmp_msginfo);
11441 g_slist_free(new_msglist);
11443 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11445 if (compose && originally_enc) {
11446 compose_force_encryption(compose, compose->account, FALSE, s_system);
11449 if (compose && originally_sig && compose->account->default_sign_reply) {
11450 compose_force_signing(compose, compose->account, s_system);
11454 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11457 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11460 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11461 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11462 GSList *cur = msginfo_list;
11463 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11464 "messages. Opening the windows "
11465 "could take some time. Do you "
11466 "want to continue?"),
11467 g_slist_length(msginfo_list));
11468 if (g_slist_length(msginfo_list) > 9
11469 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11470 != G_ALERTALTERNATE) {
11475 /* We'll open multiple compose windows */
11476 /* let the WM place the next windows */
11477 compose_force_window_origin = FALSE;
11478 for (; cur; cur = cur->next) {
11480 tmplist.data = cur->data;
11481 tmplist.next = NULL;
11482 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11484 compose_force_window_origin = TRUE;
11486 /* forwarding multiple mails as attachments is done via a
11487 * single compose window */
11488 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11492 void compose_check_for_email_account(Compose *compose)
11494 PrefsAccount *ac = NULL, *curr = NULL;
11500 if (compose->account && compose->account->protocol == A_NNTP) {
11501 ac = account_get_cur_account();
11502 if (ac->protocol == A_NNTP) {
11503 list = account_get_list();
11505 for( ; list != NULL ; list = g_list_next(list)) {
11506 curr = (PrefsAccount *) list->data;
11507 if (curr->protocol != A_NNTP) {
11513 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11518 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11519 const gchar *address)
11521 GSList *msginfo_list = NULL;
11522 gchar *body = messageview_get_selection(msgview);
11525 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11527 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11528 compose_check_for_email_account(compose);
11529 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11530 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11531 compose_reply_set_subject(compose, msginfo);
11534 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11537 void compose_set_position(Compose *compose, gint pos)
11539 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11541 gtkut_text_view_set_position(text, pos);
11544 gboolean compose_search_string(Compose *compose,
11545 const gchar *str, gboolean case_sens)
11547 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11549 return gtkut_text_view_search_string(text, str, case_sens);
11552 gboolean compose_search_string_backward(Compose *compose,
11553 const gchar *str, gboolean case_sens)
11555 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11557 return gtkut_text_view_search_string_backward(text, str, case_sens);
11560 /* allocate a msginfo structure and populate its data from a compose data structure */
11561 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11563 MsgInfo *newmsginfo;
11565 gchar buf[BUFFSIZE];
11567 cm_return_val_if_fail( compose != NULL, NULL );
11569 newmsginfo = procmsg_msginfo_new();
11572 get_rfc822_date(buf, sizeof(buf));
11573 newmsginfo->date = g_strdup(buf);
11576 if (compose->from_name) {
11577 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11578 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11582 if (compose->subject_entry)
11583 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11585 /* to, cc, reply-to, newsgroups */
11586 for (list = compose->header_list; list; list = list->next) {
11587 gchar *header = gtk_editable_get_chars(
11589 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11590 gchar *entry = gtk_editable_get_chars(
11591 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11593 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11594 if ( newmsginfo->to == NULL ) {
11595 newmsginfo->to = g_strdup(entry);
11596 } else if (entry && *entry) {
11597 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11598 g_free(newmsginfo->to);
11599 newmsginfo->to = tmp;
11602 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11603 if ( newmsginfo->cc == NULL ) {
11604 newmsginfo->cc = g_strdup(entry);
11605 } else if (entry && *entry) {
11606 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11607 g_free(newmsginfo->cc);
11608 newmsginfo->cc = tmp;
11611 if ( strcasecmp(header,
11612 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11613 if ( newmsginfo->newsgroups == NULL ) {
11614 newmsginfo->newsgroups = g_strdup(entry);
11615 } else if (entry && *entry) {
11616 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11617 g_free(newmsginfo->newsgroups);
11618 newmsginfo->newsgroups = tmp;
11626 /* other data is unset */
11632 /* update compose's dictionaries from folder dict settings */
11633 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11634 FolderItem *folder_item)
11636 cm_return_if_fail(compose != NULL);
11638 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11639 FolderItemPrefs *prefs = folder_item->prefs;
11641 if (prefs->enable_default_dictionary)
11642 gtkaspell_change_dict(compose->gtkaspell,
11643 prefs->default_dictionary, FALSE);
11644 if (folder_item->prefs->enable_default_alt_dictionary)
11645 gtkaspell_change_alt_dict(compose->gtkaspell,
11646 prefs->default_alt_dictionary);
11647 if (prefs->enable_default_dictionary
11648 || prefs->enable_default_alt_dictionary)
11649 compose_spell_menu_changed(compose);
11654 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11656 Compose *compose = (Compose *)data;
11658 cm_return_if_fail(compose != NULL);
11660 gtk_widget_grab_focus(compose->text);