2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 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 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
934 prefs_common.compose_save_to_history = add_history(
935 prefs_common.compose_save_to_history, folderidentifier);
936 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
937 prefs_common.compose_save_to_history);
940 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
941 if (folderidentifier)
942 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
944 gtk_entry_set_text(GTK_ENTRY(entry), "");
947 static gchar *compose_get_save_to(Compose *compose)
950 gchar *result = NULL;
951 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
952 result = gtk_editable_get_chars(entry, 0, -1);
955 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
956 prefs_common.compose_save_to_history = add_history(
957 prefs_common.compose_save_to_history, result);
958 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
959 prefs_common.compose_save_to_history);
964 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
965 GList *attach_files, GList *listAddress )
968 GtkTextView *textview;
969 GtkTextBuffer *textbuf;
971 const gchar *subject_format = NULL;
972 const gchar *body_format = NULL;
973 gchar *mailto_from = NULL;
974 PrefsAccount *mailto_account = NULL;
975 MsgInfo* dummyinfo = NULL;
976 gint cursor_pos = -1;
977 MailField mfield = NO_FIELD_PRESENT;
981 /* check if mailto defines a from */
982 if (mailto && *mailto != '\0') {
983 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
984 /* mailto defines a from, check if we can get account prefs from it,
985 if not, the account prefs will be guessed using other ways, but we'll keep
988 mailto_account = account_find_from_address(mailto_from, TRUE);
990 account = mailto_account;
993 /* if no account prefs set from mailto, set if from folder prefs (if any) */
994 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
995 account = account_find_from_id(item->prefs->default_account);
997 /* if no account prefs set, fallback to the current one */
998 if (!account) account = cur_account;
999 cm_return_val_if_fail(account != NULL, NULL);
1001 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 /* override from name if mailto asked for it */
1005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1006 g_free(mailto_from);
1008 /* override from name according to folder properties */
1009 if (item && item->prefs &&
1010 item->prefs->compose_with_format &&
1011 item->prefs->compose_override_from_format &&
1012 *item->prefs->compose_override_from_format != '\0') {
1017 dummyinfo = compose_msginfo_new_from_compose(compose);
1019 /* decode \-escape sequences in the internal representation of the quote format */
1020 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1021 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1024 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1025 compose->gtkaspell);
1027 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1029 quote_fmt_scan_string(tmp);
1032 buf = quote_fmt_get_buffer();
1034 alertpanel_error(_("New message From format error."));
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1037 quote_fmt_reset_vartable();
1042 compose->replyinfo = NULL;
1043 compose->fwdinfo = NULL;
1045 textview = GTK_TEXT_VIEW(compose->text);
1046 textbuf = gtk_text_view_get_buffer(textview);
1047 compose_create_tags(textview, compose);
1049 undo_block(compose->undostruct);
1051 compose_set_dictionaries_from_folder_prefs(compose, item);
1054 if (account->auto_sig)
1055 compose_insert_sig(compose, FALSE);
1056 gtk_text_buffer_get_start_iter(textbuf, &iter);
1057 gtk_text_buffer_place_cursor(textbuf, &iter);
1059 if (account->protocol != A_NNTP) {
1060 if (mailto && *mailto != '\0') {
1061 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1064 compose_set_folder_prefs(compose, item, TRUE);
1066 if (item && item->ret_rcpt) {
1067 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1070 if (mailto && *mailto != '\0') {
1071 if (!strchr(mailto, '@'))
1072 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1074 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1075 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1076 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1077 mfield = TO_FIELD_PRESENT;
1080 * CLAWS: just don't allow return receipt request, even if the user
1081 * may want to send an email. simple but foolproof.
1083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1085 compose_add_field_list( compose, listAddress );
1087 if (item && item->prefs && item->prefs->compose_with_format) {
1088 subject_format = item->prefs->compose_subject_format;
1089 body_format = item->prefs->compose_body_format;
1090 } else if (account->compose_with_format) {
1091 subject_format = account->compose_subject_format;
1092 body_format = account->compose_body_format;
1093 } else if (prefs_common.compose_with_format) {
1094 subject_format = prefs_common.compose_subject_format;
1095 body_format = prefs_common.compose_body_format;
1098 if (subject_format || body_format) {
1101 && *subject_format != '\0' )
1103 gchar *subject = NULL;
1108 dummyinfo = compose_msginfo_new_from_compose(compose);
1110 /* decode \-escape sequences in the internal representation of the quote format */
1111 tmp = g_malloc(strlen(subject_format)+1);
1112 pref_get_unescaped_pref(tmp, subject_format);
1114 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1116 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1117 compose->gtkaspell);
1119 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1121 quote_fmt_scan_string(tmp);
1124 buf = quote_fmt_get_buffer();
1126 alertpanel_error(_("New message subject format error."));
1128 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1129 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1130 quote_fmt_reset_vartable();
1134 mfield = SUBJECT_FIELD_PRESENT;
1138 && *body_format != '\0' )
1141 GtkTextBuffer *buffer;
1142 GtkTextIter start, end;
1146 dummyinfo = compose_msginfo_new_from_compose(compose);
1148 text = GTK_TEXT_VIEW(compose->text);
1149 buffer = gtk_text_view_get_buffer(text);
1150 gtk_text_buffer_get_start_iter(buffer, &start);
1151 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1152 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1154 compose_quote_fmt(compose, dummyinfo,
1156 NULL, tmp, FALSE, TRUE,
1157 _("The body of the \"New message\" template has an error at line %d."));
1158 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1159 quote_fmt_reset_vartable();
1163 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1164 gtkaspell_highlight_all(compose->gtkaspell);
1166 mfield = BODY_FIELD_PRESENT;
1170 procmsg_msginfo_free( dummyinfo );
1176 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1177 ainfo = (AttachInfo *) curr->data;
1178 compose_attach_append(compose, ainfo->file, ainfo->name,
1179 ainfo->content_type, ainfo->charset);
1183 compose_show_first_last_header(compose, TRUE);
1185 /* Set save folder */
1186 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1187 gchar *folderidentifier;
1189 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1190 folderidentifier = folder_item_get_identifier(item);
1191 compose_set_save_to(compose, folderidentifier);
1192 g_free(folderidentifier);
1195 /* Place cursor according to provided input (mfield) */
1197 case NO_FIELD_PRESENT:
1198 if (compose->header_last)
1199 gtk_widget_grab_focus(compose->header_last->entry);
1201 case TO_FIELD_PRESENT:
1202 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1204 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1207 gtk_widget_grab_focus(compose->subject_entry);
1209 case SUBJECT_FIELD_PRESENT:
1210 textview = GTK_TEXT_VIEW(compose->text);
1213 textbuf = gtk_text_view_get_buffer(textview);
1216 mark = gtk_text_buffer_get_insert(textbuf);
1217 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1218 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1220 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1221 * only defers where it comes to the variable body
1222 * is not null. If no body is present compose->text
1223 * will be null in which case you cannot place the
1224 * cursor inside the component so. An empty component
1225 * is therefore created before placing the cursor
1227 case BODY_FIELD_PRESENT:
1228 cursor_pos = quote_fmt_get_cursor_pos();
1229 if (cursor_pos == -1)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 gtk_widget_grab_focus(compose->text);
1236 undo_unblock(compose->undostruct);
1238 if (prefs_common.auto_exteditor)
1239 compose_exec_ext_editor(compose);
1241 compose->draft_timeout_tag = -1;
1242 SCROLL_TO_CURSOR(compose);
1244 compose->modified = FALSE;
1245 compose_set_title(compose);
1247 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1252 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1253 gboolean override_pref, const gchar *system)
1255 const gchar *privacy = NULL;
1257 cm_return_if_fail(compose != NULL);
1258 cm_return_if_fail(account != NULL);
1260 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1265 else if (account->default_privacy_system
1266 && strlen(account->default_privacy_system)) {
1267 privacy = account->default_privacy_system;
1269 GSList *privacy_avail = privacy_get_system_ids();
1270 if (privacy_avail && g_slist_length(privacy_avail)) {
1271 privacy = (gchar *)(privacy_avail->data);
1274 if (privacy != NULL) {
1276 g_free(compose->privacy_system);
1277 compose->privacy_system = NULL;
1279 if (compose->privacy_system == NULL)
1280 compose->privacy_system = g_strdup(privacy);
1281 else if (*(compose->privacy_system) == '\0') {
1282 g_free(compose->privacy_system);
1283 compose->privacy_system = g_strdup(privacy);
1285 compose_update_privacy_system_menu_item(compose, FALSE);
1286 compose_use_encryption(compose, TRUE);
1290 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1292 const gchar *privacy = NULL;
1296 else if (account->default_privacy_system
1297 && strlen(account->default_privacy_system)) {
1298 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1306 if (privacy != NULL) {
1308 g_free(compose->privacy_system);
1309 compose->privacy_system = NULL;
1311 if (compose->privacy_system == NULL)
1312 compose->privacy_system = g_strdup(privacy);
1313 compose_update_privacy_system_menu_item(compose, FALSE);
1314 compose_use_signing(compose, TRUE);
1318 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1322 Compose *compose = NULL;
1324 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1326 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1327 cm_return_val_if_fail(msginfo != NULL, NULL);
1329 list_len = g_slist_length(msginfo_list);
1333 case COMPOSE_REPLY_TO_ADDRESS:
1334 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1335 FALSE, prefs_common.default_reply_list, FALSE, body);
1337 case COMPOSE_REPLY_WITH_QUOTE:
1338 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1339 FALSE, prefs_common.default_reply_list, FALSE, body);
1341 case COMPOSE_REPLY_WITHOUT_QUOTE:
1342 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1343 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1345 case COMPOSE_REPLY_TO_SENDER:
1346 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1347 FALSE, FALSE, TRUE, body);
1349 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1350 compose = compose_followup_and_reply_to(msginfo,
1351 COMPOSE_QUOTE_CHECK,
1352 FALSE, FALSE, body);
1354 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1356 FALSE, FALSE, TRUE, body);
1358 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1360 FALSE, FALSE, TRUE, NULL);
1362 case COMPOSE_REPLY_TO_ALL:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1364 TRUE, FALSE, FALSE, body);
1366 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1368 TRUE, FALSE, FALSE, body);
1370 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1371 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1372 TRUE, FALSE, FALSE, NULL);
1374 case COMPOSE_REPLY_TO_LIST:
1375 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1376 FALSE, TRUE, FALSE, body);
1378 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1379 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1380 FALSE, TRUE, FALSE, body);
1382 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1383 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1384 FALSE, TRUE, FALSE, NULL);
1386 case COMPOSE_FORWARD:
1387 if (prefs_common.forward_as_attachment) {
1388 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1391 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1395 case COMPOSE_FORWARD_INLINE:
1396 /* check if we reply to more than one Message */
1397 if (list_len == 1) {
1398 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1401 /* more messages FALL THROUGH */
1402 case COMPOSE_FORWARD_AS_ATTACH:
1403 compose = compose_forward_multiple(NULL, msginfo_list);
1405 case COMPOSE_REDIRECT:
1406 compose = compose_redirect(NULL, msginfo, FALSE);
1409 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1412 if (compose == NULL) {
1413 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1417 compose->rmode = mode;
1418 switch (compose->rmode) {
1420 case COMPOSE_REPLY_WITH_QUOTE:
1421 case COMPOSE_REPLY_WITHOUT_QUOTE:
1422 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1423 debug_print("reply mode Normal\n");
1424 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1425 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1427 case COMPOSE_REPLY_TO_SENDER:
1428 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1429 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1430 debug_print("reply mode Sender\n");
1431 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1433 case COMPOSE_REPLY_TO_ALL:
1434 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1435 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1436 debug_print("reply mode All\n");
1437 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1439 case COMPOSE_REPLY_TO_LIST:
1440 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1441 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1442 debug_print("reply mode List\n");
1443 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1445 case COMPOSE_REPLY_TO_ADDRESS:
1446 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1454 static Compose *compose_reply(MsgInfo *msginfo,
1455 ComposeQuoteMode quote_mode,
1461 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1462 to_sender, FALSE, body);
1465 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1466 ComposeQuoteMode quote_mode,
1471 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1472 to_sender, TRUE, body);
1475 static void compose_extract_original_charset(Compose *compose)
1477 MsgInfo *info = NULL;
1478 if (compose->replyinfo) {
1479 info = compose->replyinfo;
1480 } else if (compose->fwdinfo) {
1481 info = compose->fwdinfo;
1482 } else if (compose->targetinfo) {
1483 info = compose->targetinfo;
1486 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1487 MimeInfo *partinfo = mimeinfo;
1488 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1489 partinfo = procmime_mimeinfo_next(partinfo);
1491 compose->orig_charset =
1492 g_strdup(procmime_mimeinfo_get_parameter(
1493 partinfo, "charset"));
1495 procmime_mimeinfo_free_all(mimeinfo);
1499 #define SIGNAL_BLOCK(buffer) { \
1500 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1501 G_CALLBACK(compose_changed_cb), \
1503 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1504 G_CALLBACK(text_inserted), \
1508 #define SIGNAL_UNBLOCK(buffer) { \
1509 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1510 G_CALLBACK(compose_changed_cb), \
1512 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1513 G_CALLBACK(text_inserted), \
1517 static Compose *compose_generic_reply(MsgInfo *msginfo,
1518 ComposeQuoteMode quote_mode,
1519 gboolean to_all, gboolean to_ml,
1521 gboolean followup_and_reply_to,
1525 PrefsAccount *account = NULL;
1526 GtkTextView *textview;
1527 GtkTextBuffer *textbuf;
1528 gboolean quote = FALSE;
1529 const gchar *qmark = NULL;
1530 const gchar *body_fmt = NULL;
1531 gchar *s_system = NULL;
1533 cm_return_val_if_fail(msginfo != NULL, NULL);
1534 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1536 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1538 cm_return_val_if_fail(account != NULL, NULL);
1540 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1542 compose->updating = TRUE;
1544 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1547 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1548 if (!compose->replyinfo)
1549 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1551 compose_extract_original_charset(compose);
1553 if (msginfo->folder && msginfo->folder->ret_rcpt)
1554 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1556 /* Set save folder */
1557 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1558 gchar *folderidentifier;
1560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1561 folderidentifier = folder_item_get_identifier(msginfo->folder);
1562 compose_set_save_to(compose, folderidentifier);
1563 g_free(folderidentifier);
1566 if (compose_parse_header(compose, msginfo) < 0) {
1567 compose->updating = FALSE;
1568 compose_destroy(compose);
1572 /* override from name according to folder properties */
1573 if (msginfo->folder && msginfo->folder->prefs &&
1574 msginfo->folder->prefs->reply_with_format &&
1575 msginfo->folder->prefs->reply_override_from_format &&
1576 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1581 /* decode \-escape sequences in the internal representation of the quote format */
1582 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1583 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1586 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1587 compose->gtkaspell);
1589 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1591 quote_fmt_scan_string(tmp);
1594 buf = quote_fmt_get_buffer();
1596 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1598 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1599 quote_fmt_reset_vartable();
1604 textview = (GTK_TEXT_VIEW(compose->text));
1605 textbuf = gtk_text_view_get_buffer(textview);
1606 compose_create_tags(textview, compose);
1608 undo_block(compose->undostruct);
1610 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1611 gtkaspell_block_check(compose->gtkaspell);
1614 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1615 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1616 /* use the reply format of folder (if enabled), or the account's one
1617 (if enabled) or fallback to the global reply format, which is always
1618 enabled (even if empty), and use the relevant quotemark */
1620 if (msginfo->folder && msginfo->folder->prefs &&
1621 msginfo->folder->prefs->reply_with_format) {
1622 qmark = msginfo->folder->prefs->reply_quotemark;
1623 body_fmt = msginfo->folder->prefs->reply_body_format;
1625 } else if (account->reply_with_format) {
1626 qmark = account->reply_quotemark;
1627 body_fmt = account->reply_body_format;
1630 qmark = prefs_common.quotemark;
1631 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1632 body_fmt = gettext(prefs_common.quotefmt);
1639 /* empty quotemark is not allowed */
1640 if (qmark == NULL || *qmark == '\0')
1642 compose_quote_fmt(compose, compose->replyinfo,
1643 body_fmt, qmark, body, FALSE, TRUE,
1644 _("The body of the \"Reply\" template has an error at line %d."));
1645 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1646 quote_fmt_reset_vartable();
1649 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1650 compose_force_encryption(compose, account, FALSE, s_system);
1653 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1654 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1655 compose_force_signing(compose, account, s_system);
1659 SIGNAL_BLOCK(textbuf);
1661 if (account->auto_sig)
1662 compose_insert_sig(compose, FALSE);
1664 compose_wrap_all(compose);
1667 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1668 gtkaspell_highlight_all(compose->gtkaspell);
1669 gtkaspell_unblock_check(compose->gtkaspell);
1671 SIGNAL_UNBLOCK(textbuf);
1673 gtk_widget_grab_focus(compose->text);
1675 undo_unblock(compose->undostruct);
1677 if (prefs_common.auto_exteditor)
1678 compose_exec_ext_editor(compose);
1680 compose->modified = FALSE;
1681 compose_set_title(compose);
1683 compose->updating = FALSE;
1684 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1685 SCROLL_TO_CURSOR(compose);
1687 if (compose->deferred_destroy) {
1688 compose_destroy(compose);
1696 #define INSERT_FW_HEADER(var, hdr) \
1697 if (msginfo->var && *msginfo->var) { \
1698 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1699 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1700 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1703 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1704 gboolean as_attach, const gchar *body,
1705 gboolean no_extedit,
1709 GtkTextView *textview;
1710 GtkTextBuffer *textbuf;
1711 gint cursor_pos = -1;
1714 cm_return_val_if_fail(msginfo != NULL, NULL);
1715 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1718 !(account = compose_guess_forward_account_from_msginfo
1720 account = cur_account;
1722 if (!prefs_common.forward_as_attachment)
1723 mode = COMPOSE_FORWARD_INLINE;
1725 mode = COMPOSE_FORWARD;
1726 compose = compose_create(account, msginfo->folder, mode, batch);
1728 compose->updating = TRUE;
1729 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1730 if (!compose->fwdinfo)
1731 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1733 compose_extract_original_charset(compose);
1735 if (msginfo->subject && *msginfo->subject) {
1736 gchar *buf, *buf2, *p;
1738 buf = p = g_strdup(msginfo->subject);
1739 p += subject_get_prefix_length(p);
1740 memmove(buf, p, strlen(p) + 1);
1742 buf2 = g_strdup_printf("Fw: %s", buf);
1743 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1749 /* override from name according to folder properties */
1750 if (msginfo->folder && msginfo->folder->prefs &&
1751 msginfo->folder->prefs->forward_with_format &&
1752 msginfo->folder->prefs->forward_override_from_format &&
1753 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1757 MsgInfo *full_msginfo = NULL;
1760 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1762 full_msginfo = procmsg_msginfo_copy(msginfo);
1764 /* decode \-escape sequences in the internal representation of the quote format */
1765 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1766 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1769 gtkaspell_block_check(compose->gtkaspell);
1770 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1771 compose->gtkaspell);
1773 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1775 quote_fmt_scan_string(tmp);
1778 buf = quote_fmt_get_buffer();
1780 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1782 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1783 quote_fmt_reset_vartable();
1786 procmsg_msginfo_free(full_msginfo);
1789 textview = GTK_TEXT_VIEW(compose->text);
1790 textbuf = gtk_text_view_get_buffer(textview);
1791 compose_create_tags(textview, compose);
1793 undo_block(compose->undostruct);
1797 msgfile = procmsg_get_message_file(msginfo);
1798 if (!is_file_exist(msgfile))
1799 g_warning("%s: file not exist\n", msgfile);
1801 compose_attach_append(compose, msgfile, msgfile,
1802 "message/rfc822", NULL);
1806 const gchar *qmark = NULL;
1807 const gchar *body_fmt = NULL;
1808 MsgInfo *full_msginfo;
1810 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1812 full_msginfo = procmsg_msginfo_copy(msginfo);
1814 /* use the forward format of folder (if enabled), or the account's one
1815 (if enabled) or fallback to the global forward format, which is always
1816 enabled (even if empty), and use the relevant quotemark */
1817 if (msginfo->folder && msginfo->folder->prefs &&
1818 msginfo->folder->prefs->forward_with_format) {
1819 qmark = msginfo->folder->prefs->forward_quotemark;
1820 body_fmt = msginfo->folder->prefs->forward_body_format;
1822 } else if (account->forward_with_format) {
1823 qmark = account->forward_quotemark;
1824 body_fmt = account->forward_body_format;
1827 qmark = prefs_common.fw_quotemark;
1828 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1829 body_fmt = gettext(prefs_common.fw_quotefmt);
1834 /* empty quotemark is not allowed */
1835 if (qmark == NULL || *qmark == '\0')
1838 compose_quote_fmt(compose, full_msginfo,
1839 body_fmt, qmark, body, FALSE, TRUE,
1840 _("The body of the \"Forward\" template has an error at line %d."));
1841 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1842 quote_fmt_reset_vartable();
1843 compose_attach_parts(compose, msginfo);
1845 procmsg_msginfo_free(full_msginfo);
1848 SIGNAL_BLOCK(textbuf);
1850 if (account->auto_sig)
1851 compose_insert_sig(compose, FALSE);
1853 compose_wrap_all(compose);
1856 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1857 gtkaspell_highlight_all(compose->gtkaspell);
1858 gtkaspell_unblock_check(compose->gtkaspell);
1860 SIGNAL_UNBLOCK(textbuf);
1862 cursor_pos = quote_fmt_get_cursor_pos();
1863 if (cursor_pos == -1)
1864 gtk_widget_grab_focus(compose->header_last->entry);
1866 gtk_widget_grab_focus(compose->text);
1868 if (!no_extedit && prefs_common.auto_exteditor)
1869 compose_exec_ext_editor(compose);
1872 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1873 gchar *folderidentifier;
1875 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1876 folderidentifier = folder_item_get_identifier(msginfo->folder);
1877 compose_set_save_to(compose, folderidentifier);
1878 g_free(folderidentifier);
1881 undo_unblock(compose->undostruct);
1883 compose->modified = FALSE;
1884 compose_set_title(compose);
1886 compose->updating = FALSE;
1887 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1888 SCROLL_TO_CURSOR(compose);
1890 if (compose->deferred_destroy) {
1891 compose_destroy(compose);
1895 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1900 #undef INSERT_FW_HEADER
1902 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1905 GtkTextView *textview;
1906 GtkTextBuffer *textbuf;
1910 gboolean single_mail = TRUE;
1912 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1914 if (g_slist_length(msginfo_list) > 1)
1915 single_mail = FALSE;
1917 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1918 if (((MsgInfo *)msginfo->data)->folder == NULL)
1921 /* guess account from first selected message */
1923 !(account = compose_guess_forward_account_from_msginfo
1924 (msginfo_list->data)))
1925 account = cur_account;
1927 cm_return_val_if_fail(account != NULL, NULL);
1929 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1930 if (msginfo->data) {
1931 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1932 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1936 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1937 g_warning("no msginfo_list");
1941 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1943 compose->updating = TRUE;
1945 /* override from name according to folder properties */
1946 if (msginfo_list->data) {
1947 MsgInfo *msginfo = msginfo_list->data;
1949 if (msginfo->folder && msginfo->folder->prefs &&
1950 msginfo->folder->prefs->forward_with_format &&
1951 msginfo->folder->prefs->forward_override_from_format &&
1952 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1957 /* decode \-escape sequences in the internal representation of the quote format */
1958 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1959 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1962 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1963 compose->gtkaspell);
1965 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1967 quote_fmt_scan_string(tmp);
1970 buf = quote_fmt_get_buffer();
1972 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1975 quote_fmt_reset_vartable();
1981 textview = GTK_TEXT_VIEW(compose->text);
1982 textbuf = gtk_text_view_get_buffer(textview);
1983 compose_create_tags(textview, compose);
1985 undo_block(compose->undostruct);
1986 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1987 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1989 if (!is_file_exist(msgfile))
1990 g_warning("%s: file not exist\n", msgfile);
1992 compose_attach_append(compose, msgfile, msgfile,
1993 "message/rfc822", NULL);
1998 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1999 if (info->subject && *info->subject) {
2000 gchar *buf, *buf2, *p;
2002 buf = p = g_strdup(info->subject);
2003 p += subject_get_prefix_length(p);
2004 memmove(buf, p, strlen(p) + 1);
2006 buf2 = g_strdup_printf("Fw: %s", buf);
2007 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2013 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2014 _("Fw: multiple emails"));
2017 SIGNAL_BLOCK(textbuf);
2019 if (account->auto_sig)
2020 compose_insert_sig(compose, FALSE);
2022 compose_wrap_all(compose);
2024 SIGNAL_UNBLOCK(textbuf);
2026 gtk_text_buffer_get_start_iter(textbuf, &iter);
2027 gtk_text_buffer_place_cursor(textbuf, &iter);
2029 gtk_widget_grab_focus(compose->header_last->entry);
2030 undo_unblock(compose->undostruct);
2031 compose->modified = FALSE;
2032 compose_set_title(compose);
2034 compose->updating = FALSE;
2035 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2036 SCROLL_TO_CURSOR(compose);
2038 if (compose->deferred_destroy) {
2039 compose_destroy(compose);
2043 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2048 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2050 GtkTextIter start = *iter;
2051 GtkTextIter end_iter;
2052 int start_pos = gtk_text_iter_get_offset(&start);
2054 if (!compose->account->sig_sep)
2057 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2058 start_pos+strlen(compose->account->sig_sep));
2060 /* check sig separator */
2061 str = gtk_text_iter_get_text(&start, &end_iter);
2062 if (!strcmp(str, compose->account->sig_sep)) {
2064 /* check end of line (\n) */
2065 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2066 start_pos+strlen(compose->account->sig_sep));
2067 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2068 start_pos+strlen(compose->account->sig_sep)+1);
2069 tmp = gtk_text_iter_get_text(&start, &end_iter);
2070 if (!strcmp(tmp,"\n")) {
2082 static void compose_colorize_signature(Compose *compose)
2084 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2086 GtkTextIter end_iter;
2087 gtk_text_buffer_get_start_iter(buffer, &iter);
2088 while (gtk_text_iter_forward_line(&iter))
2089 if (compose_is_sig_separator(compose, buffer, &iter)) {
2090 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2091 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2095 #define BLOCK_WRAP() { \
2096 prev_autowrap = compose->autowrap; \
2097 buffer = gtk_text_view_get_buffer( \
2098 GTK_TEXT_VIEW(compose->text)); \
2099 compose->autowrap = FALSE; \
2101 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2102 G_CALLBACK(compose_changed_cb), \
2104 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2105 G_CALLBACK(text_inserted), \
2108 #define UNBLOCK_WRAP() { \
2109 compose->autowrap = prev_autowrap; \
2110 if (compose->autowrap) { \
2111 gint old = compose->draft_timeout_tag; \
2112 compose->draft_timeout_tag = -2; \
2113 compose_wrap_all(compose); \
2114 compose->draft_timeout_tag = old; \
2117 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2118 G_CALLBACK(compose_changed_cb), \
2120 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2121 G_CALLBACK(text_inserted), \
2125 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2127 Compose *compose = NULL;
2128 PrefsAccount *account = NULL;
2129 GtkTextView *textview;
2130 GtkTextBuffer *textbuf;
2134 gchar buf[BUFFSIZE];
2135 gboolean use_signing = FALSE;
2136 gboolean use_encryption = FALSE;
2137 gchar *privacy_system = NULL;
2138 int priority = PRIORITY_NORMAL;
2139 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2140 gboolean autowrap = prefs_common.autowrap;
2141 gboolean autoindent = prefs_common.auto_indent;
2142 HeaderEntry *manual_headers = NULL;
2144 cm_return_val_if_fail(msginfo != NULL, NULL);
2145 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2147 if (compose_put_existing_to_front(msginfo)) {
2151 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2152 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2153 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2154 gchar queueheader_buf[BUFFSIZE];
2157 /* Select Account from queue headers */
2158 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2159 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2160 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2161 account = account_find_from_id(id);
2163 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2164 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2165 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2166 account = account_find_from_id(id);
2168 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2169 sizeof(queueheader_buf), "NAID:")) {
2170 id = atoi(&queueheader_buf[strlen("NAID:")]);
2171 account = account_find_from_id(id);
2173 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2174 sizeof(queueheader_buf), "MAID:")) {
2175 id = atoi(&queueheader_buf[strlen("MAID:")]);
2176 account = account_find_from_id(id);
2178 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2179 sizeof(queueheader_buf), "S:")) {
2180 account = account_find_from_address(queueheader_buf, FALSE);
2182 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2183 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2184 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2185 use_signing = param;
2188 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2189 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2190 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2191 use_signing = param;
2194 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2195 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2196 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2197 use_encryption = param;
2199 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2201 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2202 use_encryption = param;
2204 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2205 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2206 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2209 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2210 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2211 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2214 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2215 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2216 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2218 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2219 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2220 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2222 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2223 sizeof(queueheader_buf), "X-Priority: ")) {
2224 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2227 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2228 sizeof(queueheader_buf), "RMID:")) {
2229 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2230 if (tokens[0] && tokens[1] && tokens[2]) {
2231 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2232 if (orig_item != NULL) {
2233 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2238 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2239 sizeof(queueheader_buf), "FMID:")) {
2240 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2241 if (tokens[0] && tokens[1] && tokens[2]) {
2242 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2243 if (orig_item != NULL) {
2244 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2249 /* Get manual headers */
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2251 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2252 if (*listmh != '\0') {
2253 debug_print("Got manual headers: %s\n", listmh);
2254 manual_headers = procheader_entries_from_str(listmh);
2259 account = msginfo->folder->folder->account;
2262 if (!account && prefs_common.reedit_account_autosel) {
2263 gchar from[BUFFSIZE];
2264 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2265 extract_address(from);
2266 account = account_find_from_address(from, FALSE);
2270 account = cur_account;
2272 cm_return_val_if_fail(account != NULL, NULL);
2274 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2276 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2277 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2278 compose->autowrap = autowrap;
2279 compose->replyinfo = replyinfo;
2280 compose->fwdinfo = fwdinfo;
2282 compose->updating = TRUE;
2283 compose->priority = priority;
2285 if (privacy_system != NULL) {
2286 compose->privacy_system = privacy_system;
2287 compose_use_signing(compose, use_signing);
2288 compose_use_encryption(compose, use_encryption);
2289 compose_update_privacy_system_menu_item(compose, FALSE);
2291 activate_privacy_system(compose, account, FALSE);
2294 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2296 compose_extract_original_charset(compose);
2298 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2299 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2300 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2301 gchar queueheader_buf[BUFFSIZE];
2303 /* Set message save folder */
2304 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2305 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2306 compose_set_save_to(compose, &queueheader_buf[4]);
2308 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2309 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2311 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2316 if (compose_parse_header(compose, msginfo) < 0) {
2317 compose->updating = FALSE;
2318 compose_destroy(compose);
2321 compose_reedit_set_entry(compose, msginfo);
2323 textview = GTK_TEXT_VIEW(compose->text);
2324 textbuf = gtk_text_view_get_buffer(textview);
2325 compose_create_tags(textview, compose);
2327 mark = gtk_text_buffer_get_insert(textbuf);
2328 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2330 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2331 G_CALLBACK(compose_changed_cb),
2334 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2335 fp = procmime_get_first_encrypted_text_content(msginfo);
2337 compose_force_encryption(compose, account, TRUE, NULL);
2340 fp = procmime_get_first_text_content(msginfo);
2343 g_warning("Can't get text part\n");
2347 gboolean prev_autowrap;
2348 GtkTextBuffer *buffer;
2350 while (fgets(buf, sizeof(buf), fp) != NULL) {
2352 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2358 compose_attach_parts(compose, msginfo);
2360 compose_colorize_signature(compose);
2362 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2363 G_CALLBACK(compose_changed_cb),
2366 if (manual_headers != NULL) {
2367 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2368 procheader_entries_free(manual_headers);
2369 compose->updating = FALSE;
2370 compose_destroy(compose);
2373 procheader_entries_free(manual_headers);
2376 gtk_widget_grab_focus(compose->text);
2378 if (prefs_common.auto_exteditor) {
2379 compose_exec_ext_editor(compose);
2381 compose->modified = FALSE;
2382 compose_set_title(compose);
2384 compose->updating = FALSE;
2385 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2386 SCROLL_TO_CURSOR(compose);
2388 if (compose->deferred_destroy) {
2389 compose_destroy(compose);
2393 compose->sig_str = account_get_signature_str(compose->account);
2395 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2400 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2407 cm_return_val_if_fail(msginfo != NULL, NULL);
2410 account = account_get_reply_account(msginfo,
2411 prefs_common.reply_account_autosel);
2412 cm_return_val_if_fail(account != NULL, NULL);
2414 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2416 compose->updating = TRUE;
2418 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2419 compose->replyinfo = NULL;
2420 compose->fwdinfo = NULL;
2422 compose_show_first_last_header(compose, TRUE);
2424 gtk_widget_grab_focus(compose->header_last->entry);
2426 filename = procmsg_get_message_file(msginfo);
2428 if (filename == NULL) {
2429 compose->updating = FALSE;
2430 compose_destroy(compose);
2435 compose->redirect_filename = filename;
2437 /* Set save folder */
2438 item = msginfo->folder;
2439 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2440 gchar *folderidentifier;
2442 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2443 folderidentifier = folder_item_get_identifier(item);
2444 compose_set_save_to(compose, folderidentifier);
2445 g_free(folderidentifier);
2448 compose_attach_parts(compose, msginfo);
2450 if (msginfo->subject)
2451 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2453 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2455 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2456 _("The body of the \"Redirect\" template has an error at line %d."));
2457 quote_fmt_reset_vartable();
2458 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2460 compose_colorize_signature(compose);
2463 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2464 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2465 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2470 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2471 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2476 if (compose->toolbar->draft_btn)
2477 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2478 if (compose->toolbar->insert_btn)
2479 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2480 if (compose->toolbar->attach_btn)
2481 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2482 if (compose->toolbar->sig_btn)
2483 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2484 if (compose->toolbar->exteditor_btn)
2485 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2486 if (compose->toolbar->linewrap_current_btn)
2487 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2488 if (compose->toolbar->linewrap_all_btn)
2489 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2491 compose->modified = FALSE;
2492 compose_set_title(compose);
2493 compose->updating = FALSE;
2494 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2495 SCROLL_TO_CURSOR(compose);
2497 if (compose->deferred_destroy) {
2498 compose_destroy(compose);
2502 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2507 GList *compose_get_compose_list(void)
2509 return compose_list;
2512 void compose_entry_append(Compose *compose, const gchar *address,
2513 ComposeEntryType type, ComposePrefType pref_type)
2515 const gchar *header;
2517 gboolean in_quote = FALSE;
2518 if (!address || *address == '\0') return;
2525 header = N_("Bcc:");
2527 case COMPOSE_REPLYTO:
2528 header = N_("Reply-To:");
2530 case COMPOSE_NEWSGROUPS:
2531 header = N_("Newsgroups:");
2533 case COMPOSE_FOLLOWUPTO:
2534 header = N_( "Followup-To:");
2536 case COMPOSE_INREPLYTO:
2537 header = N_( "In-Reply-To:");
2544 header = prefs_common_translated_header_name(header);
2546 cur = begin = (gchar *)address;
2548 /* we separate the line by commas, but not if we're inside a quoted
2550 while (*cur != '\0') {
2552 in_quote = !in_quote;
2553 if (*cur == ',' && !in_quote) {
2554 gchar *tmp = g_strdup(begin);
2556 tmp[cur-begin]='\0';
2559 while (*tmp == ' ' || *tmp == '\t')
2561 compose_add_header_entry(compose, header, tmp, pref_type);
2568 gchar *tmp = g_strdup(begin);
2570 tmp[cur-begin]='\0';
2571 while (*tmp == ' ' || *tmp == '\t')
2573 compose_add_header_entry(compose, header, tmp, pref_type);
2578 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2580 #if !GTK_CHECK_VERSION(3, 0, 0)
2581 static GdkColor yellow;
2582 static GdkColor black;
2583 static gboolean yellow_initialised = FALSE;
2585 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2586 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2591 #if !GTK_CHECK_VERSION(3, 0, 0)
2592 if (!yellow_initialised) {
2593 gdk_color_parse("#f5f6be", &yellow);
2594 gdk_color_parse("#000000", &black);
2595 yellow_initialised = gdk_colormap_alloc_color(
2596 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2597 yellow_initialised &= gdk_colormap_alloc_color(
2598 gdk_colormap_get_system(), &black, FALSE, TRUE);
2602 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2603 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2604 if (gtk_entry_get_text(entry) &&
2605 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2606 #if !GTK_CHECK_VERSION(3, 0, 0)
2607 if (yellow_initialised) {
2609 gtk_widget_modify_base(
2610 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2611 GTK_STATE_NORMAL, &yellow);
2612 gtk_widget_modify_text(
2613 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2614 GTK_STATE_NORMAL, &black);
2615 #if !GTK_CHECK_VERSION(3, 0, 0)
2622 void compose_toolbar_cb(gint action, gpointer data)
2624 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2625 Compose *compose = (Compose*)toolbar_item->parent;
2627 cm_return_if_fail(compose != NULL);
2631 compose_send_cb(NULL, compose);
2634 compose_send_later_cb(NULL, compose);
2637 compose_draft(compose, COMPOSE_QUIT_EDITING);
2640 compose_insert_file_cb(NULL, compose);
2643 compose_attach_cb(NULL, compose);
2646 compose_insert_sig(compose, FALSE);
2649 compose_ext_editor_cb(NULL, compose);
2651 case A_LINEWRAP_CURRENT:
2652 compose_beautify_paragraph(compose, NULL, TRUE);
2654 case A_LINEWRAP_ALL:
2655 compose_wrap_all_full(compose, TRUE);
2658 compose_address_cb(NULL, compose);
2661 case A_CHECK_SPELLING:
2662 compose_check_all(NULL, compose);
2670 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2675 gchar *subject = NULL;
2679 gchar **attach = NULL;
2680 gchar *inreplyto = NULL;
2681 MailField mfield = NO_FIELD_PRESENT;
2683 /* get mailto parts but skip from */
2684 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2687 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2688 mfield = TO_FIELD_PRESENT;
2691 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2693 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2695 if (!g_utf8_validate (subject, -1, NULL)) {
2696 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2697 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2700 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2702 mfield = SUBJECT_FIELD_PRESENT;
2705 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2706 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2709 gboolean prev_autowrap = compose->autowrap;
2711 compose->autowrap = FALSE;
2713 mark = gtk_text_buffer_get_insert(buffer);
2714 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2716 if (!g_utf8_validate (body, -1, NULL)) {
2717 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2718 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2721 gtk_text_buffer_insert(buffer, &iter, body, -1);
2723 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2725 compose->autowrap = prev_autowrap;
2726 if (compose->autowrap)
2727 compose_wrap_all(compose);
2728 mfield = BODY_FIELD_PRESENT;
2732 gint i = 0, att = 0;
2733 gchar *warn_files = NULL;
2734 while (attach[i] != NULL) {
2735 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2736 if (utf8_filename) {
2737 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2738 gchar *tmp = g_strdup_printf("%s%s\n",
2739 warn_files?warn_files:"",
2745 g_free(utf8_filename);
2747 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2752 alertpanel_notice(ngettext(
2753 "The following file has been attached: \n%s",
2754 "The following files have been attached: \n%s", att), warn_files);
2759 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2772 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2774 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2775 {"Cc:", NULL, TRUE},
2776 {"References:", NULL, FALSE},
2777 {"Bcc:", NULL, TRUE},
2778 {"Newsgroups:", NULL, TRUE},
2779 {"Followup-To:", NULL, TRUE},
2780 {"List-Post:", NULL, FALSE},
2781 {"X-Priority:", NULL, FALSE},
2782 {NULL, NULL, FALSE}};
2798 cm_return_val_if_fail(msginfo != NULL, -1);
2800 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2801 procheader_get_header_fields(fp, hentry);
2804 if (hentry[H_REPLY_TO].body != NULL) {
2805 if (hentry[H_REPLY_TO].body[0] != '\0') {
2807 conv_unmime_header(hentry[H_REPLY_TO].body,
2810 g_free(hentry[H_REPLY_TO].body);
2811 hentry[H_REPLY_TO].body = NULL;
2813 if (hentry[H_CC].body != NULL) {
2814 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2815 g_free(hentry[H_CC].body);
2816 hentry[H_CC].body = NULL;
2818 if (hentry[H_REFERENCES].body != NULL) {
2819 if (compose->mode == COMPOSE_REEDIT)
2820 compose->references = hentry[H_REFERENCES].body;
2822 compose->references = compose_parse_references
2823 (hentry[H_REFERENCES].body, msginfo->msgid);
2824 g_free(hentry[H_REFERENCES].body);
2826 hentry[H_REFERENCES].body = NULL;
2828 if (hentry[H_BCC].body != NULL) {
2829 if (compose->mode == COMPOSE_REEDIT)
2831 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2832 g_free(hentry[H_BCC].body);
2833 hentry[H_BCC].body = NULL;
2835 if (hentry[H_NEWSGROUPS].body != NULL) {
2836 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2837 hentry[H_NEWSGROUPS].body = NULL;
2839 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2840 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2841 compose->followup_to =
2842 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2845 g_free(hentry[H_FOLLOWUP_TO].body);
2846 hentry[H_FOLLOWUP_TO].body = NULL;
2848 if (hentry[H_LIST_POST].body != NULL) {
2849 gchar *to = NULL, *start = NULL;
2851 extract_address(hentry[H_LIST_POST].body);
2852 if (hentry[H_LIST_POST].body[0] != '\0') {
2853 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2855 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2856 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2859 g_free(compose->ml_post);
2860 compose->ml_post = to;
2863 g_free(hentry[H_LIST_POST].body);
2864 hentry[H_LIST_POST].body = NULL;
2867 /* CLAWS - X-Priority */
2868 if (compose->mode == COMPOSE_REEDIT)
2869 if (hentry[H_X_PRIORITY].body != NULL) {
2872 priority = atoi(hentry[H_X_PRIORITY].body);
2873 g_free(hentry[H_X_PRIORITY].body);
2875 hentry[H_X_PRIORITY].body = NULL;
2877 if (priority < PRIORITY_HIGHEST ||
2878 priority > PRIORITY_LOWEST)
2879 priority = PRIORITY_NORMAL;
2881 compose->priority = priority;
2884 if (compose->mode == COMPOSE_REEDIT) {
2885 if (msginfo->inreplyto && *msginfo->inreplyto)
2886 compose->inreplyto = g_strdup(msginfo->inreplyto);
2890 if (msginfo->msgid && *msginfo->msgid)
2891 compose->inreplyto = g_strdup(msginfo->msgid);
2893 if (!compose->references) {
2894 if (msginfo->msgid && *msginfo->msgid) {
2895 if (msginfo->inreplyto && *msginfo->inreplyto)
2896 compose->references =
2897 g_strdup_printf("<%s>\n\t<%s>",
2901 compose->references =
2902 g_strconcat("<", msginfo->msgid, ">",
2904 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2905 compose->references =
2906 g_strconcat("<", msginfo->inreplyto, ">",
2914 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2919 cm_return_val_if_fail(msginfo != NULL, -1);
2921 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2922 procheader_get_header_fields(fp, entries);
2926 while (he != NULL && he->name != NULL) {
2928 GtkListStore *model = NULL;
2930 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2931 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2932 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2933 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2934 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2941 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2943 GSList *ref_id_list, *cur;
2947 ref_id_list = references_list_append(NULL, ref);
2948 if (!ref_id_list) return NULL;
2949 if (msgid && *msgid)
2950 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2955 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2956 /* "<" + Message-ID + ">" + CR+LF+TAB */
2957 len += strlen((gchar *)cur->data) + 5;
2959 if (len > MAX_REFERENCES_LEN) {
2960 /* remove second message-ID */
2961 if (ref_id_list && ref_id_list->next &&
2962 ref_id_list->next->next) {
2963 g_free(ref_id_list->next->data);
2964 ref_id_list = g_slist_remove
2965 (ref_id_list, ref_id_list->next->data);
2967 slist_free_strings_full(ref_id_list);
2974 new_ref = g_string_new("");
2975 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2976 if (new_ref->len > 0)
2977 g_string_append(new_ref, "\n\t");
2978 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2981 slist_free_strings_full(ref_id_list);
2983 new_ref_str = new_ref->str;
2984 g_string_free(new_ref, FALSE);
2989 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2990 const gchar *fmt, const gchar *qmark,
2991 const gchar *body, gboolean rewrap,
2992 gboolean need_unescape,
2993 const gchar *err_msg)
2995 MsgInfo* dummyinfo = NULL;
2996 gchar *quote_str = NULL;
2998 gboolean prev_autowrap;
2999 const gchar *trimmed_body = body;
3000 gint cursor_pos = -1;
3001 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3002 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3007 SIGNAL_BLOCK(buffer);
3010 dummyinfo = compose_msginfo_new_from_compose(compose);
3011 msginfo = dummyinfo;
3014 if (qmark != NULL) {
3016 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3017 compose->gtkaspell);
3019 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3021 quote_fmt_scan_string(qmark);
3024 buf = quote_fmt_get_buffer();
3026 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3028 Xstrdup_a(quote_str, buf, goto error)
3031 if (fmt && *fmt != '\0') {
3034 while (*trimmed_body == '\n')
3038 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3039 compose->gtkaspell);
3041 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3043 if (need_unescape) {
3046 /* decode \-escape sequences in the internal representation of the quote format */
3047 tmp = g_malloc(strlen(fmt)+1);
3048 pref_get_unescaped_pref(tmp, fmt);
3049 quote_fmt_scan_string(tmp);
3053 quote_fmt_scan_string(fmt);
3057 buf = quote_fmt_get_buffer();
3059 gint line = quote_fmt_get_line();
3060 alertpanel_error(err_msg, line);
3066 prev_autowrap = compose->autowrap;
3067 compose->autowrap = FALSE;
3069 mark = gtk_text_buffer_get_insert(buffer);
3070 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3071 if (g_utf8_validate(buf, -1, NULL)) {
3072 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3074 gchar *tmpout = NULL;
3075 tmpout = conv_codeset_strdup
3076 (buf, conv_get_locale_charset_str_no_utf8(),
3078 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3080 tmpout = g_malloc(strlen(buf)*2+1);
3081 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3083 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3087 cursor_pos = quote_fmt_get_cursor_pos();
3088 if (cursor_pos == -1)
3089 cursor_pos = gtk_text_iter_get_offset(&iter);
3090 compose->set_cursor_pos = cursor_pos;
3092 gtk_text_buffer_get_start_iter(buffer, &iter);
3093 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3094 gtk_text_buffer_place_cursor(buffer, &iter);
3096 compose->autowrap = prev_autowrap;
3097 if (compose->autowrap && rewrap)
3098 compose_wrap_all(compose);
3105 SIGNAL_UNBLOCK(buffer);
3107 procmsg_msginfo_free( dummyinfo );
3112 /* if ml_post is of type addr@host and from is of type
3113 * addr-anything@host, return TRUE
3115 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3117 gchar *left_ml = NULL;
3118 gchar *right_ml = NULL;
3119 gchar *left_from = NULL;
3120 gchar *right_from = NULL;
3121 gboolean result = FALSE;
3123 if (!ml_post || !from)
3126 left_ml = g_strdup(ml_post);
3127 if (strstr(left_ml, "@")) {
3128 right_ml = strstr(left_ml, "@")+1;
3129 *(strstr(left_ml, "@")) = '\0';
3132 left_from = g_strdup(from);
3133 if (strstr(left_from, "@")) {
3134 right_from = strstr(left_from, "@")+1;
3135 *(strstr(left_from, "@")) = '\0';
3138 if (left_ml && left_from && right_ml && right_from
3139 && !strncmp(left_from, left_ml, strlen(left_ml))
3140 && !strcmp(right_from, right_ml)) {
3149 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3150 gboolean respect_default_to)
3154 if (!folder || !folder->prefs)
3157 if (respect_default_to && folder->prefs->enable_default_to) {
3158 compose_entry_append(compose, folder->prefs->default_to,
3159 COMPOSE_TO, PREF_FOLDER);
3160 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3162 if (folder->prefs->enable_default_cc)
3163 compose_entry_append(compose, folder->prefs->default_cc,
3164 COMPOSE_CC, PREF_FOLDER);
3165 if (folder->prefs->enable_default_bcc)
3166 compose_entry_append(compose, folder->prefs->default_bcc,
3167 COMPOSE_BCC, PREF_FOLDER);
3168 if (folder->prefs->enable_default_replyto)
3169 compose_entry_append(compose, folder->prefs->default_replyto,
3170 COMPOSE_REPLYTO, PREF_FOLDER);
3173 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3178 if (!compose || !msginfo)
3181 if (msginfo->subject && *msginfo->subject) {
3182 buf = p = g_strdup(msginfo->subject);
3183 p += subject_get_prefix_length(p);
3184 memmove(buf, p, strlen(p) + 1);
3186 buf2 = g_strdup_printf("Re: %s", buf);
3187 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3192 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3195 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3196 gboolean to_all, gboolean to_ml,
3198 gboolean followup_and_reply_to)
3200 GSList *cc_list = NULL;
3203 gchar *replyto = NULL;
3204 gchar *ac_email = NULL;
3206 gboolean reply_to_ml = FALSE;
3207 gboolean default_reply_to = FALSE;
3209 cm_return_if_fail(compose->account != NULL);
3210 cm_return_if_fail(msginfo != NULL);
3212 reply_to_ml = to_ml && compose->ml_post;
3214 default_reply_to = msginfo->folder &&
3215 msginfo->folder->prefs->enable_default_reply_to;
3217 if (compose->account->protocol != A_NNTP) {
3218 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3220 if (reply_to_ml && !default_reply_to) {
3222 gboolean is_subscr = is_subscription(compose->ml_post,
3225 /* normal answer to ml post with a reply-to */
3226 compose_entry_append(compose,
3228 COMPOSE_TO, PREF_ML);
3229 if (compose->replyto)
3230 compose_entry_append(compose,
3232 COMPOSE_CC, PREF_ML);
3234 /* answer to subscription confirmation */
3235 if (compose->replyto)
3236 compose_entry_append(compose,
3238 COMPOSE_TO, PREF_ML);
3239 else if (msginfo->from)
3240 compose_entry_append(compose,
3242 COMPOSE_TO, PREF_ML);
3245 else if (!(to_all || to_sender) && default_reply_to) {
3246 compose_entry_append(compose,
3247 msginfo->folder->prefs->default_reply_to,
3248 COMPOSE_TO, PREF_FOLDER);
3249 compose_entry_mark_default_to(compose,
3250 msginfo->folder->prefs->default_reply_to);
3255 Xstrdup_a(tmp1, msginfo->from, return);
3256 extract_address(tmp1);
3257 if (to_all || to_sender ||
3258 !account_find_from_address(tmp1, FALSE))
3259 compose_entry_append(compose,
3260 (compose->replyto && !to_sender)
3261 ? compose->replyto :
3262 msginfo->from ? msginfo->from : "",
3263 COMPOSE_TO, PREF_NONE);
3264 else if (!to_all && !to_sender) {
3265 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3266 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3267 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3268 if (compose->replyto) {
3269 compose_entry_append(compose,
3271 COMPOSE_TO, PREF_NONE);
3273 compose_entry_append(compose,
3274 msginfo->from ? msginfo->from : "",
3275 COMPOSE_TO, PREF_NONE);
3278 /* replying to own mail, use original recp */
3279 compose_entry_append(compose,
3280 msginfo->to ? msginfo->to : "",
3281 COMPOSE_TO, PREF_NONE);
3282 compose_entry_append(compose,
3283 msginfo->cc ? msginfo->cc : "",
3284 COMPOSE_CC, PREF_NONE);
3289 if (to_sender || (compose->followup_to &&
3290 !strncmp(compose->followup_to, "poster", 6)))
3291 compose_entry_append
3293 (compose->replyto ? compose->replyto :
3294 msginfo->from ? msginfo->from : ""),
3295 COMPOSE_TO, PREF_NONE);
3297 else if (followup_and_reply_to || to_all) {
3298 compose_entry_append
3300 (compose->replyto ? compose->replyto :
3301 msginfo->from ? msginfo->from : ""),
3302 COMPOSE_TO, PREF_NONE);
3304 compose_entry_append
3306 compose->followup_to ? compose->followup_to :
3307 compose->newsgroups ? compose->newsgroups : "",
3308 COMPOSE_NEWSGROUPS, PREF_NONE);
3311 compose_entry_append
3313 compose->followup_to ? compose->followup_to :
3314 compose->newsgroups ? compose->newsgroups : "",
3315 COMPOSE_NEWSGROUPS, PREF_NONE);
3317 compose_reply_set_subject(compose, msginfo);
3319 if (to_ml && compose->ml_post) return;
3320 if (!to_all || compose->account->protocol == A_NNTP) return;
3322 if (compose->replyto) {
3323 Xstrdup_a(replyto, compose->replyto, return);
3324 extract_address(replyto);
3326 if (msginfo->from) {
3327 Xstrdup_a(from, msginfo->from, return);
3328 extract_address(from);
3331 if (replyto && from)
3332 cc_list = address_list_append_with_comments(cc_list, from);
3333 if (to_all && msginfo->folder &&
3334 msginfo->folder->prefs->enable_default_reply_to)
3335 cc_list = address_list_append_with_comments(cc_list,
3336 msginfo->folder->prefs->default_reply_to);
3337 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3338 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3340 ac_email = g_utf8_strdown(compose->account->address, -1);
3343 for (cur = cc_list; cur != NULL; cur = cur->next) {
3344 gchar *addr = g_utf8_strdown(cur->data, -1);
3345 extract_address(addr);
3347 if (strcmp(ac_email, addr))
3348 compose_entry_append(compose, (gchar *)cur->data,
3349 COMPOSE_CC, PREF_NONE);
3351 debug_print("Cc address same as compose account's, ignoring\n");
3356 slist_free_strings_full(cc_list);
3362 #define SET_ENTRY(entry, str) \
3365 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3368 #define SET_ADDRESS(type, str) \
3371 compose_entry_append(compose, str, type, PREF_NONE); \
3374 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3376 cm_return_if_fail(msginfo != NULL);
3378 SET_ENTRY(subject_entry, msginfo->subject);
3379 SET_ENTRY(from_name, msginfo->from);
3380 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3381 SET_ADDRESS(COMPOSE_CC, compose->cc);
3382 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3383 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3384 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3385 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3387 compose_update_priority_menu_item(compose);
3388 compose_update_privacy_system_menu_item(compose, FALSE);
3389 compose_show_first_last_header(compose, TRUE);
3395 static void compose_insert_sig(Compose *compose, gboolean replace)
3397 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3398 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3400 GtkTextIter iter, iter_end;
3401 gint cur_pos, ins_pos;
3402 gboolean prev_autowrap;
3403 gboolean found = FALSE;
3404 gboolean exists = FALSE;
3406 cm_return_if_fail(compose->account != NULL);
3410 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3411 G_CALLBACK(compose_changed_cb),
3414 mark = gtk_text_buffer_get_insert(buffer);
3415 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3416 cur_pos = gtk_text_iter_get_offset (&iter);
3419 gtk_text_buffer_get_end_iter(buffer, &iter);
3421 exists = (compose->sig_str != NULL);
3424 GtkTextIter first_iter, start_iter, end_iter;
3426 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3428 if (!exists || compose->sig_str[0] == '\0')
3431 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3432 compose->signature_tag);
3435 /* include previous \n\n */
3436 gtk_text_iter_backward_chars(&first_iter, 1);
3437 start_iter = first_iter;
3438 end_iter = first_iter;
3440 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3441 compose->signature_tag);
3442 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3443 compose->signature_tag);
3445 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3451 g_free(compose->sig_str);
3452 compose->sig_str = account_get_signature_str(compose->account);
3454 cur_pos = gtk_text_iter_get_offset(&iter);
3456 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3457 g_free(compose->sig_str);
3458 compose->sig_str = NULL;
3460 if (compose->sig_inserted == FALSE)
3461 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3462 compose->sig_inserted = TRUE;
3464 cur_pos = gtk_text_iter_get_offset(&iter);
3465 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3467 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3468 gtk_text_iter_forward_chars(&iter, 1);
3469 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3470 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3472 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3473 cur_pos = gtk_text_buffer_get_char_count (buffer);
3476 /* put the cursor where it should be
3477 * either where the quote_fmt says, either where it was */
3478 if (compose->set_cursor_pos < 0)
3479 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3481 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3482 compose->set_cursor_pos);
3484 compose->set_cursor_pos = -1;
3485 gtk_text_buffer_place_cursor(buffer, &iter);
3486 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3487 G_CALLBACK(compose_changed_cb),
3493 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3496 GtkTextBuffer *buffer;
3499 const gchar *cur_encoding;
3500 gchar buf[BUFFSIZE];
3503 gboolean prev_autowrap;
3504 gboolean badtxt = FALSE;
3505 struct stat file_stat;
3508 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3510 /* get the size of the file we are about to insert */
3511 ret = g_stat(file, &file_stat);
3513 gchar *shortfile = g_path_get_basename(file);
3514 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3516 return COMPOSE_INSERT_NO_FILE;
3517 } else if (prefs_common.warn_large_insert == TRUE) {
3519 /* ask user for confirmation if the file is large */
3520 if (prefs_common.warn_large_insert_size < 0 ||
3521 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3525 msg = g_strdup_printf(_("You are about to insert a file of %s "
3526 "in the message body. Are you sure you want to do that?"),
3527 to_human_readable(file_stat.st_size));
3528 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3529 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3532 /* do we ask for confirmation next time? */
3533 if (aval & G_ALERTDISABLE) {
3534 /* no confirmation next time, disable feature in preferences */
3535 aval &= ~G_ALERTDISABLE;
3536 prefs_common.warn_large_insert = FALSE;
3539 /* abort file insertion if user canceled action */
3540 if (aval != G_ALERTALTERNATE) {
3541 return COMPOSE_INSERT_NO_FILE;
3547 if ((fp = g_fopen(file, "rb")) == NULL) {
3548 FILE_OP_ERROR(file, "fopen");
3549 return COMPOSE_INSERT_READ_ERROR;
3552 prev_autowrap = compose->autowrap;
3553 compose->autowrap = FALSE;
3555 text = GTK_TEXT_VIEW(compose->text);
3556 buffer = gtk_text_view_get_buffer(text);
3557 mark = gtk_text_buffer_get_insert(buffer);
3558 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3560 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3561 G_CALLBACK(text_inserted),
3564 cur_encoding = conv_get_locale_charset_str_no_utf8();
3566 while (fgets(buf, sizeof(buf), fp) != NULL) {
3569 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3570 str = g_strdup(buf);
3572 str = conv_codeset_strdup
3573 (buf, cur_encoding, CS_INTERNAL);
3576 /* strip <CR> if DOS/Windows file,
3577 replace <CR> with <LF> if Macintosh file. */
3580 if (len > 0 && str[len - 1] != '\n') {
3582 if (str[len] == '\r') str[len] = '\n';
3585 gtk_text_buffer_insert(buffer, &iter, str, -1);
3589 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3590 G_CALLBACK(text_inserted),
3592 compose->autowrap = prev_autowrap;
3593 if (compose->autowrap)
3594 compose_wrap_all(compose);
3599 return COMPOSE_INSERT_INVALID_CHARACTER;
3601 return COMPOSE_INSERT_SUCCESS;
3604 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3605 const gchar *filename,
3606 const gchar *content_type,
3607 const gchar *charset)
3615 GtkListStore *store;
3617 gboolean has_binary = FALSE;
3619 if (!is_file_exist(file)) {
3620 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3621 gboolean result = FALSE;
3622 if (file_from_uri && is_file_exist(file_from_uri)) {
3623 result = compose_attach_append(
3624 compose, file_from_uri,
3625 filename, content_type,
3628 g_free(file_from_uri);
3631 alertpanel_error("File %s doesn't exist\n", filename);
3634 if ((size = get_file_size(file)) < 0) {
3635 alertpanel_error("Can't get file size of %s\n", filename);
3639 alertpanel_error(_("File %s is empty."), filename);
3642 if ((fp = g_fopen(file, "rb")) == NULL) {
3643 alertpanel_error(_("Can't read %s."), filename);
3648 ainfo = g_new0(AttachInfo, 1);
3649 auto_ainfo = g_auto_pointer_new_with_free
3650 (ainfo, (GFreeFunc) compose_attach_info_free);
3651 ainfo->file = g_strdup(file);
3654 ainfo->content_type = g_strdup(content_type);
3655 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3657 MsgFlags flags = {0, 0};
3659 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3660 ainfo->encoding = ENC_7BIT;
3662 ainfo->encoding = ENC_8BIT;
3664 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3665 if (msginfo && msginfo->subject)
3666 name = g_strdup(msginfo->subject);
3668 name = g_path_get_basename(filename ? filename : file);
3670 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3672 procmsg_msginfo_free(msginfo);
3674 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3675 ainfo->charset = g_strdup(charset);
3676 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3678 ainfo->encoding = ENC_BASE64;
3680 name = g_path_get_basename(filename ? filename : file);
3681 ainfo->name = g_strdup(name);
3685 ainfo->content_type = procmime_get_mime_type(file);
3686 if (!ainfo->content_type) {
3687 ainfo->content_type =
3688 g_strdup("application/octet-stream");
3689 ainfo->encoding = ENC_BASE64;
3690 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3692 procmime_get_encoding_for_text_file(file, &has_binary);
3694 ainfo->encoding = ENC_BASE64;
3695 name = g_path_get_basename(filename ? filename : file);
3696 ainfo->name = g_strdup(name);
3700 if (ainfo->name != NULL
3701 && !strcmp(ainfo->name, ".")) {
3702 g_free(ainfo->name);
3706 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3707 g_free(ainfo->content_type);
3708 ainfo->content_type = g_strdup("application/octet-stream");
3709 g_free(ainfo->charset);
3710 ainfo->charset = NULL;
3713 ainfo->size = (goffset)size;
3714 size_text = to_human_readable((goffset)size);
3716 store = GTK_LIST_STORE(gtk_tree_view_get_model
3717 (GTK_TREE_VIEW(compose->attach_clist)));
3719 gtk_list_store_append(store, &iter);
3720 gtk_list_store_set(store, &iter,
3721 COL_MIMETYPE, ainfo->content_type,
3722 COL_SIZE, size_text,
3723 COL_NAME, ainfo->name,
3724 COL_CHARSET, ainfo->charset,
3726 COL_AUTODATA, auto_ainfo,
3729 g_auto_pointer_free(auto_ainfo);
3730 compose_attach_update_label(compose);
3734 static void compose_use_signing(Compose *compose, gboolean use_signing)
3736 compose->use_signing = use_signing;
3737 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3740 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3742 compose->use_encryption = use_encryption;
3743 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3746 #define NEXT_PART_NOT_CHILD(info) \
3748 node = info->node; \
3749 while (node->children) \
3750 node = g_node_last_child(node); \
3751 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3754 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3758 MimeInfo *firsttext = NULL;
3759 MimeInfo *encrypted = NULL;
3762 const gchar *partname = NULL;
3764 mimeinfo = procmime_scan_message(msginfo);
3765 if (!mimeinfo) return;
3767 if (mimeinfo->node->children == NULL) {
3768 procmime_mimeinfo_free_all(mimeinfo);
3772 /* find first content part */
3773 child = (MimeInfo *) mimeinfo->node->children->data;
3774 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3775 child = (MimeInfo *)child->node->children->data;
3778 if (child->type == MIMETYPE_TEXT) {
3780 debug_print("First text part found\n");
3781 } else if (compose->mode == COMPOSE_REEDIT &&
3782 child->type == MIMETYPE_APPLICATION &&
3783 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3784 encrypted = (MimeInfo *)child->node->parent->data;
3787 child = (MimeInfo *) mimeinfo->node->children->data;
3788 while (child != NULL) {
3791 if (child == encrypted) {
3792 /* skip this part of tree */
3793 NEXT_PART_NOT_CHILD(child);
3797 if (child->type == MIMETYPE_MULTIPART) {
3798 /* get the actual content */
3799 child = procmime_mimeinfo_next(child);
3803 if (child == firsttext) {
3804 child = procmime_mimeinfo_next(child);
3808 outfile = procmime_get_tmp_file_name(child);
3809 if ((err = procmime_get_part(outfile, child)) < 0)
3810 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3812 gchar *content_type;
3814 content_type = procmime_get_content_type_str(child->type, child->subtype);
3816 /* if we meet a pgp signature, we don't attach it, but
3817 * we force signing. */
3818 if ((strcmp(content_type, "application/pgp-signature") &&
3819 strcmp(content_type, "application/pkcs7-signature") &&
3820 strcmp(content_type, "application/x-pkcs7-signature"))
3821 || compose->mode == COMPOSE_REDIRECT) {
3822 partname = procmime_mimeinfo_get_parameter(child, "filename");
3823 if (partname == NULL)
3824 partname = procmime_mimeinfo_get_parameter(child, "name");
3825 if (partname == NULL)
3827 compose_attach_append(compose, outfile,
3828 partname, content_type,
3829 procmime_mimeinfo_get_parameter(child, "charset"));
3831 compose_force_signing(compose, compose->account, NULL);
3833 g_free(content_type);
3836 NEXT_PART_NOT_CHILD(child);
3838 procmime_mimeinfo_free_all(mimeinfo);
3841 #undef NEXT_PART_NOT_CHILD
3846 WAIT_FOR_INDENT_CHAR,
3847 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3850 /* return indent length, we allow:
3851 indent characters followed by indent characters or spaces/tabs,
3852 alphabets and numbers immediately followed by indent characters,
3853 and the repeating sequences of the above
3854 If quote ends with multiple spaces, only the first one is included. */
3855 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3856 const GtkTextIter *start, gint *len)
3858 GtkTextIter iter = *start;
3862 IndentState state = WAIT_FOR_INDENT_CHAR;
3865 gint alnum_count = 0;
3866 gint space_count = 0;
3869 if (prefs_common.quote_chars == NULL) {
3873 while (!gtk_text_iter_ends_line(&iter)) {
3874 wc = gtk_text_iter_get_char(&iter);
3875 if (g_unichar_iswide(wc))
3877 clen = g_unichar_to_utf8(wc, ch);
3881 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3882 is_space = g_unichar_isspace(wc);
3884 if (state == WAIT_FOR_INDENT_CHAR) {
3885 if (!is_indent && !g_unichar_isalnum(wc))
3888 quote_len += alnum_count + space_count + 1;
3889 alnum_count = space_count = 0;
3890 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3893 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3894 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3898 else if (is_indent) {
3899 quote_len += alnum_count + space_count + 1;
3900 alnum_count = space_count = 0;
3903 state = WAIT_FOR_INDENT_CHAR;
3907 gtk_text_iter_forward_char(&iter);
3910 if (quote_len > 0 && space_count > 0)
3916 if (quote_len > 0) {
3918 gtk_text_iter_forward_chars(&iter, quote_len);
3919 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3925 /* return >0 if the line is itemized */
3926 static int compose_itemized_length(GtkTextBuffer *buffer,
3927 const GtkTextIter *start)
3929 GtkTextIter iter = *start;
3934 if (gtk_text_iter_ends_line(&iter))
3939 wc = gtk_text_iter_get_char(&iter);
3940 if (!g_unichar_isspace(wc))
3942 gtk_text_iter_forward_char(&iter);
3943 if (gtk_text_iter_ends_line(&iter))
3947 clen = g_unichar_to_utf8(wc, ch);
3951 if (!strchr("*-+", ch[0]))
3954 gtk_text_iter_forward_char(&iter);
3955 if (gtk_text_iter_ends_line(&iter))
3957 wc = gtk_text_iter_get_char(&iter);
3958 if (g_unichar_isspace(wc)) {
3964 /* return the string at the start of the itemization */
3965 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3966 const GtkTextIter *start)
3968 GtkTextIter iter = *start;
3971 GString *item_chars = g_string_new("");
3974 if (gtk_text_iter_ends_line(&iter))
3979 wc = gtk_text_iter_get_char(&iter);
3980 if (!g_unichar_isspace(wc))
3982 gtk_text_iter_forward_char(&iter);
3983 if (gtk_text_iter_ends_line(&iter))
3985 g_string_append_unichar(item_chars, wc);
3988 str = item_chars->str;
3989 g_string_free(item_chars, FALSE);
3993 /* return the number of spaces at a line's start */
3994 static int compose_left_offset_length(GtkTextBuffer *buffer,
3995 const GtkTextIter *start)
3997 GtkTextIter iter = *start;
4000 if (gtk_text_iter_ends_line(&iter))
4004 wc = gtk_text_iter_get_char(&iter);
4005 if (!g_unichar_isspace(wc))
4008 gtk_text_iter_forward_char(&iter);
4009 if (gtk_text_iter_ends_line(&iter))
4013 gtk_text_iter_forward_char(&iter);
4014 if (gtk_text_iter_ends_line(&iter))
4019 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4020 const GtkTextIter *start,
4021 GtkTextIter *break_pos,
4025 GtkTextIter iter = *start, line_end = *start;
4026 PangoLogAttr *attrs;
4033 gboolean can_break = FALSE;
4034 gboolean do_break = FALSE;
4035 gboolean was_white = FALSE;
4036 gboolean prev_dont_break = FALSE;
4038 gtk_text_iter_forward_to_line_end(&line_end);
4039 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4040 len = g_utf8_strlen(str, -1);
4044 g_warning("compose_get_line_break_pos: len = 0!\n");
4048 /* g_print("breaking line: %d: %s (len = %d)\n",
4049 gtk_text_iter_get_line(&iter), str, len); */
4051 attrs = g_new(PangoLogAttr, len + 1);
4053 pango_default_break(str, -1, NULL, attrs, len + 1);
4057 /* skip quote and leading spaces */
4058 for (i = 0; *p != '\0' && i < len; i++) {
4061 wc = g_utf8_get_char(p);
4062 if (i >= quote_len && !g_unichar_isspace(wc))
4064 if (g_unichar_iswide(wc))
4066 else if (*p == '\t')
4070 p = g_utf8_next_char(p);
4073 for (; *p != '\0' && i < len; i++) {
4074 PangoLogAttr *attr = attrs + i;
4078 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4081 was_white = attr->is_white;
4083 /* don't wrap URI */
4084 if ((uri_len = get_uri_len(p)) > 0) {
4086 if (pos > 0 && col > max_col) {
4096 wc = g_utf8_get_char(p);
4097 if (g_unichar_iswide(wc)) {
4099 if (prev_dont_break && can_break && attr->is_line_break)
4101 } else if (*p == '\t')
4105 if (pos > 0 && col > max_col) {
4110 if (*p == '-' || *p == '/')
4111 prev_dont_break = TRUE;
4113 prev_dont_break = FALSE;
4115 p = g_utf8_next_char(p);
4119 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4124 *break_pos = *start;
4125 gtk_text_iter_set_line_offset(break_pos, pos);
4130 static gboolean compose_join_next_line(Compose *compose,
4131 GtkTextBuffer *buffer,
4133 const gchar *quote_str)
4135 GtkTextIter iter_ = *iter, cur, prev, next, end;
4136 PangoLogAttr attrs[3];
4138 gchar *next_quote_str;
4141 gboolean keep_cursor = FALSE;
4143 if (!gtk_text_iter_forward_line(&iter_) ||
4144 gtk_text_iter_ends_line(&iter_)) {
4147 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4149 if ((quote_str || next_quote_str) &&
4150 strcmp2(quote_str, next_quote_str) != 0) {
4151 g_free(next_quote_str);
4154 g_free(next_quote_str);
4157 if (quote_len > 0) {
4158 gtk_text_iter_forward_chars(&end, quote_len);
4159 if (gtk_text_iter_ends_line(&end)) {
4164 /* don't join itemized lines */
4165 if (compose_itemized_length(buffer, &end) > 0) {
4169 /* don't join signature separator */
4170 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4173 /* delete quote str */
4175 gtk_text_buffer_delete(buffer, &iter_, &end);
4177 /* don't join line breaks put by the user */
4179 gtk_text_iter_backward_char(&cur);
4180 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4181 gtk_text_iter_forward_char(&cur);
4185 gtk_text_iter_forward_char(&cur);
4186 /* delete linebreak and extra spaces */
4187 while (gtk_text_iter_backward_char(&cur)) {
4188 wc1 = gtk_text_iter_get_char(&cur);
4189 if (!g_unichar_isspace(wc1))
4194 while (!gtk_text_iter_ends_line(&cur)) {
4195 wc1 = gtk_text_iter_get_char(&cur);
4196 if (!g_unichar_isspace(wc1))
4198 gtk_text_iter_forward_char(&cur);
4201 if (!gtk_text_iter_equal(&prev, &next)) {
4204 mark = gtk_text_buffer_get_insert(buffer);
4205 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4206 if (gtk_text_iter_equal(&prev, &cur))
4208 gtk_text_buffer_delete(buffer, &prev, &next);
4212 /* insert space if required */
4213 gtk_text_iter_backward_char(&prev);
4214 wc1 = gtk_text_iter_get_char(&prev);
4215 wc2 = gtk_text_iter_get_char(&next);
4216 gtk_text_iter_forward_char(&next);
4217 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4218 pango_default_break(str, -1, NULL, attrs, 3);
4219 if (!attrs[1].is_line_break ||
4220 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4221 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4223 gtk_text_iter_backward_char(&iter_);
4224 gtk_text_buffer_place_cursor(buffer, &iter_);
4233 #define ADD_TXT_POS(bp_, ep_, pti_) \
4234 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4235 last = last->next; \
4236 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4237 last->next = NULL; \
4239 g_warning("alloc error scanning URIs\n"); \
4242 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4244 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4245 GtkTextBuffer *buffer;
4246 GtkTextIter iter, break_pos, end_of_line;
4247 gchar *quote_str = NULL;
4249 gboolean wrap_quote = prefs_common.linewrap_quote;
4250 gboolean prev_autowrap = compose->autowrap;
4251 gint startq_offset = -1, noq_offset = -1;
4252 gint uri_start = -1, uri_stop = -1;
4253 gint nouri_start = -1, nouri_stop = -1;
4254 gint num_blocks = 0;
4255 gint quotelevel = -1;
4256 gboolean modified = force;
4257 gboolean removed = FALSE;
4258 gboolean modified_before_remove = FALSE;
4260 gboolean start = TRUE;
4261 gint itemized_len = 0, rem_item_len = 0;
4262 gchar *itemized_chars = NULL;
4263 gboolean item_continuation = FALSE;
4268 if (compose->draft_timeout_tag == -2) {
4272 compose->autowrap = FALSE;
4274 buffer = gtk_text_view_get_buffer(text);
4275 undo_wrapping(compose->undostruct, TRUE);
4280 mark = gtk_text_buffer_get_insert(buffer);
4281 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4285 if (compose->draft_timeout_tag == -2) {
4286 if (gtk_text_iter_ends_line(&iter)) {
4287 while (gtk_text_iter_ends_line(&iter) &&
4288 gtk_text_iter_forward_line(&iter))
4291 while (gtk_text_iter_backward_line(&iter)) {
4292 if (gtk_text_iter_ends_line(&iter)) {
4293 gtk_text_iter_forward_line(&iter);
4299 /* move to line start */
4300 gtk_text_iter_set_line_offset(&iter, 0);
4303 itemized_len = compose_itemized_length(buffer, &iter);
4305 if (!itemized_len) {
4306 itemized_len = compose_left_offset_length(buffer, &iter);
4307 item_continuation = TRUE;
4311 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4313 /* go until paragraph end (empty line) */
4314 while (start || !gtk_text_iter_ends_line(&iter)) {
4315 gchar *scanpos = NULL;
4316 /* parse table - in order of priority */
4318 const gchar *needle; /* token */
4320 /* token search function */
4321 gchar *(*search) (const gchar *haystack,
4322 const gchar *needle);
4323 /* part parsing function */
4324 gboolean (*parse) (const gchar *start,
4325 const gchar *scanpos,
4329 /* part to URI function */
4330 gchar *(*build_uri) (const gchar *bp,
4334 static struct table parser[] = {
4335 {"http://", strcasestr, get_uri_part, make_uri_string},
4336 {"https://", strcasestr, get_uri_part, make_uri_string},
4337 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4338 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4339 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4340 {"www.", strcasestr, get_uri_part, make_http_string},
4341 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4342 {"@", strcasestr, get_email_part, make_email_string}
4344 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4345 gint last_index = PARSE_ELEMS;
4347 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4351 if (!prev_autowrap && num_blocks == 0) {
4353 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4354 G_CALLBACK(text_inserted),
4357 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4360 uri_start = uri_stop = -1;
4362 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4365 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4366 if (startq_offset == -1)
4367 startq_offset = gtk_text_iter_get_offset(&iter);
4368 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4369 if (quotelevel > 2) {
4370 /* recycle colors */
4371 if (prefs_common.recycle_quote_colors)
4380 if (startq_offset == -1)
4381 noq_offset = gtk_text_iter_get_offset(&iter);
4385 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4388 if (gtk_text_iter_ends_line(&iter)) {
4390 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4391 prefs_common.linewrap_len,
4393 GtkTextIter prev, next, cur;
4394 if (prev_autowrap != FALSE || force) {
4395 compose->automatic_break = TRUE;
4397 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4398 compose->automatic_break = FALSE;
4399 if (itemized_len && compose->autoindent) {
4400 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4401 if (!item_continuation)
4402 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4404 } else if (quote_str && wrap_quote) {
4405 compose->automatic_break = TRUE;
4407 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4408 compose->automatic_break = FALSE;
4409 if (itemized_len && compose->autoindent) {
4410 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4411 if (!item_continuation)
4412 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4416 /* remove trailing spaces */
4418 rem_item_len = itemized_len;
4419 while (compose->autoindent && rem_item_len-- > 0)
4420 gtk_text_iter_backward_char(&cur);
4421 gtk_text_iter_backward_char(&cur);
4424 while (!gtk_text_iter_starts_line(&cur)) {
4427 gtk_text_iter_backward_char(&cur);
4428 wc = gtk_text_iter_get_char(&cur);
4429 if (!g_unichar_isspace(wc))
4433 if (!gtk_text_iter_equal(&prev, &next)) {
4434 gtk_text_buffer_delete(buffer, &prev, &next);
4436 gtk_text_iter_forward_char(&break_pos);
4440 gtk_text_buffer_insert(buffer, &break_pos,
4444 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4446 /* move iter to current line start */
4447 gtk_text_iter_set_line_offset(&iter, 0);
4454 /* move iter to next line start */
4460 if (!prev_autowrap && num_blocks > 0) {
4462 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4463 G_CALLBACK(text_inserted),
4467 while (!gtk_text_iter_ends_line(&end_of_line)) {
4468 gtk_text_iter_forward_char(&end_of_line);
4470 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4472 nouri_start = gtk_text_iter_get_offset(&iter);
4473 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4475 walk_pos = gtk_text_iter_get_offset(&iter);
4476 /* FIXME: this looks phony. scanning for anything in the parse table */
4477 for (n = 0; n < PARSE_ELEMS; n++) {
4480 tmp = parser[n].search(walk, parser[n].needle);
4482 if (scanpos == NULL || tmp < scanpos) {
4491 /* check if URI can be parsed */
4492 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4493 (const gchar **)&ep, FALSE)
4494 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4498 strlen(parser[last_index].needle);
4501 uri_start = walk_pos + (bp - o_walk);
4502 uri_stop = walk_pos + (ep - o_walk);
4506 gtk_text_iter_forward_line(&iter);
4509 if (startq_offset != -1) {
4510 GtkTextIter startquote, endquote;
4511 gtk_text_buffer_get_iter_at_offset(
4512 buffer, &startquote, startq_offset);
4515 switch (quotelevel) {
4517 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4518 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4519 gtk_text_buffer_apply_tag_by_name(
4520 buffer, "quote0", &startquote, &endquote);
4521 gtk_text_buffer_remove_tag_by_name(
4522 buffer, "quote1", &startquote, &endquote);
4523 gtk_text_buffer_remove_tag_by_name(
4524 buffer, "quote2", &startquote, &endquote);
4529 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4530 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4531 gtk_text_buffer_apply_tag_by_name(
4532 buffer, "quote1", &startquote, &endquote);
4533 gtk_text_buffer_remove_tag_by_name(
4534 buffer, "quote0", &startquote, &endquote);
4535 gtk_text_buffer_remove_tag_by_name(
4536 buffer, "quote2", &startquote, &endquote);
4541 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4542 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4543 gtk_text_buffer_apply_tag_by_name(
4544 buffer, "quote2", &startquote, &endquote);
4545 gtk_text_buffer_remove_tag_by_name(
4546 buffer, "quote0", &startquote, &endquote);
4547 gtk_text_buffer_remove_tag_by_name(
4548 buffer, "quote1", &startquote, &endquote);
4554 } else if (noq_offset != -1) {
4555 GtkTextIter startnoquote, endnoquote;
4556 gtk_text_buffer_get_iter_at_offset(
4557 buffer, &startnoquote, noq_offset);
4560 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4561 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4562 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4563 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4564 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4565 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4566 gtk_text_buffer_remove_tag_by_name(
4567 buffer, "quote0", &startnoquote, &endnoquote);
4568 gtk_text_buffer_remove_tag_by_name(
4569 buffer, "quote1", &startnoquote, &endnoquote);
4570 gtk_text_buffer_remove_tag_by_name(
4571 buffer, "quote2", &startnoquote, &endnoquote);
4577 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4578 GtkTextIter nouri_start_iter, nouri_end_iter;
4579 gtk_text_buffer_get_iter_at_offset(
4580 buffer, &nouri_start_iter, nouri_start);
4581 gtk_text_buffer_get_iter_at_offset(
4582 buffer, &nouri_end_iter, nouri_stop);
4583 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4584 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4585 gtk_text_buffer_remove_tag_by_name(
4586 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4587 modified_before_remove = modified;
4592 if (uri_start >= 0 && uri_stop > 0) {
4593 GtkTextIter uri_start_iter, uri_end_iter, back;
4594 gtk_text_buffer_get_iter_at_offset(
4595 buffer, &uri_start_iter, uri_start);
4596 gtk_text_buffer_get_iter_at_offset(
4597 buffer, &uri_end_iter, uri_stop);
4598 back = uri_end_iter;
4599 gtk_text_iter_backward_char(&back);
4600 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4601 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4602 gtk_text_buffer_apply_tag_by_name(
4603 buffer, "link", &uri_start_iter, &uri_end_iter);
4605 if (removed && !modified_before_remove) {
4611 // debug_print("not modified, out after %d lines\n", lines);
4615 // debug_print("modified, out after %d lines\n", lines);
4617 g_free(itemized_chars);
4620 undo_wrapping(compose->undostruct, FALSE);
4621 compose->autowrap = prev_autowrap;
4626 void compose_action_cb(void *data)
4628 Compose *compose = (Compose *)data;
4629 compose_wrap_all(compose);
4632 static void compose_wrap_all(Compose *compose)
4634 compose_wrap_all_full(compose, FALSE);
4637 static void compose_wrap_all_full(Compose *compose, gboolean force)
4639 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4640 GtkTextBuffer *buffer;
4642 gboolean modified = TRUE;
4644 buffer = gtk_text_view_get_buffer(text);
4646 gtk_text_buffer_get_start_iter(buffer, &iter);
4647 while (!gtk_text_iter_is_end(&iter) && modified)
4648 modified = compose_beautify_paragraph(compose, &iter, force);
4652 static void compose_set_title(Compose *compose)
4658 edited = compose->modified ? _(" [Edited]") : "";
4660 subject = gtk_editable_get_chars(
4661 GTK_EDITABLE(compose->subject_entry), 0, -1);
4663 #ifndef GENERIC_UMPC
4664 if (subject && strlen(subject))
4665 str = g_strdup_printf(_("%s - Compose message%s"),
4668 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4670 str = g_strdup(_("Compose message"));
4673 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4679 * compose_current_mail_account:
4681 * Find a current mail account (the currently selected account, or the
4682 * default account, if a news account is currently selected). If a
4683 * mail account cannot be found, display an error message.
4685 * Return value: Mail account, or NULL if not found.
4687 static PrefsAccount *
4688 compose_current_mail_account(void)
4692 if (cur_account && cur_account->protocol != A_NNTP)
4695 ac = account_get_default();
4696 if (!ac || ac->protocol == A_NNTP) {
4697 alertpanel_error(_("Account for sending mail is not specified.\n"
4698 "Please select a mail account before sending."));
4705 #define QUOTE_IF_REQUIRED(out, str) \
4707 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4711 len = strlen(str) + 3; \
4712 if ((__tmp = alloca(len)) == NULL) { \
4713 g_warning("can't allocate memory\n"); \
4714 g_string_free(header, TRUE); \
4717 g_snprintf(__tmp, len, "\"%s\"", str); \
4722 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4723 g_warning("can't allocate memory\n"); \
4724 g_string_free(header, TRUE); \
4727 strcpy(__tmp, str); \
4733 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4735 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4739 len = strlen(str) + 3; \
4740 if ((__tmp = alloca(len)) == NULL) { \
4741 g_warning("can't allocate memory\n"); \
4744 g_snprintf(__tmp, len, "\"%s\"", str); \
4749 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4750 g_warning("can't allocate memory\n"); \
4753 strcpy(__tmp, str); \
4759 static void compose_select_account(Compose *compose, PrefsAccount *account,
4762 gchar *from = NULL, *header = NULL;
4763 ComposeHeaderEntry *header_entry;
4764 #if GTK_CHECK_VERSION(2, 24, 0)
4768 cm_return_if_fail(account != NULL);
4770 compose->account = account;
4771 if (account->name && *account->name) {
4773 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4774 from = g_strdup_printf("%s <%s>",
4775 buf, account->address);
4776 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4778 from = g_strdup_printf("<%s>",
4780 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4785 compose_set_title(compose);
4787 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4788 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4790 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4791 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4792 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4794 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4796 activate_privacy_system(compose, account, FALSE);
4798 if (!init && compose->mode != COMPOSE_REDIRECT) {
4799 undo_block(compose->undostruct);
4800 compose_insert_sig(compose, TRUE);
4801 undo_unblock(compose->undostruct);
4804 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4805 #if !GTK_CHECK_VERSION(2, 24, 0)
4806 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4808 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4809 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4810 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4813 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4814 if (account->protocol == A_NNTP) {
4815 if (!strcmp(header, _("To:")))
4816 combobox_select_by_text(
4817 GTK_COMBO_BOX(header_entry->combo),
4820 if (!strcmp(header, _("Newsgroups:")))
4821 combobox_select_by_text(
4822 GTK_COMBO_BOX(header_entry->combo),
4830 /* use account's dict info if set */
4831 if (compose->gtkaspell) {
4832 if (account->enable_default_dictionary)
4833 gtkaspell_change_dict(compose->gtkaspell,
4834 account->default_dictionary, FALSE);
4835 if (account->enable_default_alt_dictionary)
4836 gtkaspell_change_alt_dict(compose->gtkaspell,
4837 account->default_alt_dictionary);
4838 if (account->enable_default_dictionary
4839 || account->enable_default_alt_dictionary)
4840 compose_spell_menu_changed(compose);
4845 gboolean compose_check_for_valid_recipient(Compose *compose) {
4846 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4847 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4848 gboolean recipient_found = FALSE;
4852 /* free to and newsgroup list */
4853 slist_free_strings_full(compose->to_list);
4854 compose->to_list = NULL;
4856 slist_free_strings_full(compose->newsgroup_list);
4857 compose->newsgroup_list = NULL;
4859 /* search header entries for to and newsgroup entries */
4860 for (list = compose->header_list; list; list = list->next) {
4863 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4864 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4867 if (entry[0] != '\0') {
4868 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4869 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4870 compose->to_list = address_list_append(compose->to_list, entry);
4871 recipient_found = TRUE;
4874 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4875 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4876 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4877 recipient_found = TRUE;
4884 return recipient_found;
4887 static gboolean compose_check_for_set_recipients(Compose *compose)
4889 if (compose->account->set_autocc && compose->account->auto_cc) {
4890 gboolean found_other = FALSE;
4892 /* search header entries for to and newsgroup entries */
4893 for (list = compose->header_list; list; list = list->next) {
4896 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4897 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4900 if (strcmp(entry, compose->account->auto_cc)
4901 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4911 if (compose->batch) {
4912 gtk_widget_show_all(compose->window);
4914 aval = alertpanel(_("Send"),
4915 _("The only recipient is the default CC address. Send anyway?"),
4916 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4917 if (aval != G_ALERTALTERNATE)
4921 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4922 gboolean found_other = FALSE;
4924 /* search header entries for to and newsgroup entries */
4925 for (list = compose->header_list; list; list = list->next) {
4928 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4929 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4932 if (strcmp(entry, compose->account->auto_bcc)
4933 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4943 if (compose->batch) {
4944 gtk_widget_show_all(compose->window);
4946 aval = alertpanel(_("Send"),
4947 _("The only recipient is the default BCC address. Send anyway?"),
4948 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4949 if (aval != G_ALERTALTERNATE)
4956 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4960 if (compose_check_for_valid_recipient(compose) == FALSE) {
4961 if (compose->batch) {
4962 gtk_widget_show_all(compose->window);
4964 alertpanel_error(_("Recipient is not specified."));
4968 if (compose_check_for_set_recipients(compose) == FALSE) {
4972 if (!compose->batch) {
4973 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4974 if (*str == '\0' && check_everything == TRUE &&
4975 compose->mode != COMPOSE_REDIRECT) {
4977 gchar *button_label;
4980 if (compose->sending)
4981 button_label = _("+_Send");
4983 button_label = _("+_Queue");
4984 message = g_strdup_printf(_("Subject is empty. %s"),
4985 compose->sending?_("Send it anyway?"):
4986 _("Queue it anyway?"));
4988 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4989 GTK_STOCK_CANCEL, button_label, NULL);
4991 if (aval != G_ALERTALTERNATE)
4996 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5002 gint compose_send(Compose *compose)
5005 FolderItem *folder = NULL;
5007 gchar *msgpath = NULL;
5008 gboolean discard_window = FALSE;
5009 gchar *errstr = NULL;
5010 gchar *tmsgid = NULL;
5011 MainWindow *mainwin = mainwindow_get_mainwindow();
5012 gboolean queued_removed = FALSE;
5014 if (prefs_common.send_dialog_invisible
5015 || compose->batch == TRUE)
5016 discard_window = TRUE;
5018 compose_allow_user_actions (compose, FALSE);
5019 compose->sending = TRUE;
5021 if (compose_check_entries(compose, TRUE) == FALSE) {
5022 if (compose->batch) {
5023 gtk_widget_show_all(compose->window);
5029 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5032 if (compose->batch) {
5033 gtk_widget_show_all(compose->window);
5036 alertpanel_error(_("Could not queue message for sending:\n\n"
5037 "Charset conversion failed."));
5038 } else if (val == -5) {
5039 alertpanel_error(_("Could not queue message for sending:\n\n"
5040 "Couldn't get recipient encryption key."));
5041 } else if (val == -6) {
5043 } else if (val == -3) {
5044 if (privacy_peek_error())
5045 alertpanel_error(_("Could not queue message for sending:\n\n"
5046 "Signature failed: %s"), privacy_get_error());
5047 } else if (val == -2 && errno != 0) {
5048 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5050 alertpanel_error(_("Could not queue message for sending."));
5055 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5056 if (discard_window) {
5057 compose->sending = FALSE;
5058 compose_close(compose);
5059 /* No more compose access in the normal codepath
5060 * after this point! */
5065 alertpanel_error(_("The message was queued but could not be "
5066 "sent.\nUse \"Send queued messages\" from "
5067 "the main window to retry."));
5068 if (!discard_window) {
5075 if (msgpath == NULL) {
5076 msgpath = folder_item_fetch_msg(folder, msgnum);
5077 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5080 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5081 claws_unlink(msgpath);
5084 if (!discard_window) {
5086 if (!queued_removed)
5087 folder_item_remove_msg(folder, msgnum);
5088 folder_item_scan(folder);
5090 /* make sure we delete that */
5091 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5093 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5094 folder_item_remove_msg(folder, tmp->msgnum);
5095 procmsg_msginfo_free(tmp);
5102 if (!queued_removed)
5103 folder_item_remove_msg(folder, msgnum);
5104 folder_item_scan(folder);
5106 /* make sure we delete that */
5107 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5109 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5110 folder_item_remove_msg(folder, tmp->msgnum);
5111 procmsg_msginfo_free(tmp);
5114 if (!discard_window) {
5115 compose->sending = FALSE;
5116 compose_allow_user_actions (compose, TRUE);
5117 compose_close(compose);
5121 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5122 "the main window to retry."), errstr);
5125 alertpanel_error_log(_("The message was queued but could not be "
5126 "sent.\nUse \"Send queued messages\" from "
5127 "the main window to retry."));
5129 if (!discard_window) {
5138 toolbar_main_set_sensitive(mainwin);
5139 main_window_set_menu_sensitive(mainwin);
5145 compose_allow_user_actions (compose, TRUE);
5146 compose->sending = FALSE;
5147 compose->modified = TRUE;
5148 toolbar_main_set_sensitive(mainwin);
5149 main_window_set_menu_sensitive(mainwin);
5154 static gboolean compose_use_attach(Compose *compose)
5156 GtkTreeModel *model = gtk_tree_view_get_model
5157 (GTK_TREE_VIEW(compose->attach_clist));
5158 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5161 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5164 gchar buf[BUFFSIZE];
5166 gboolean first_to_address;
5167 gboolean first_cc_address;
5169 ComposeHeaderEntry *headerentry;
5170 const gchar *headerentryname;
5171 const gchar *cc_hdr;
5172 const gchar *to_hdr;
5173 gboolean err = FALSE;
5175 debug_print("Writing redirect header\n");
5177 cc_hdr = prefs_common_translated_header_name("Cc:");
5178 to_hdr = prefs_common_translated_header_name("To:");
5180 first_to_address = TRUE;
5181 for (list = compose->header_list; list; list = list->next) {
5182 headerentry = ((ComposeHeaderEntry *)list->data);
5183 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5185 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5186 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5187 Xstrdup_a(str, entstr, return -1);
5189 if (str[0] != '\0') {
5190 compose_convert_header
5191 (compose, buf, sizeof(buf), str,
5192 strlen("Resent-To") + 2, TRUE);
5194 if (first_to_address) {
5195 err |= (fprintf(fp, "Resent-To: ") < 0);
5196 first_to_address = FALSE;
5198 err |= (fprintf(fp, ",") < 0);
5200 err |= (fprintf(fp, "%s", buf) < 0);
5204 if (!first_to_address) {
5205 err |= (fprintf(fp, "\n") < 0);
5208 first_cc_address = TRUE;
5209 for (list = compose->header_list; list; list = list->next) {
5210 headerentry = ((ComposeHeaderEntry *)list->data);
5211 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5213 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5214 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5215 Xstrdup_a(str, strg, return -1);
5217 if (str[0] != '\0') {
5218 compose_convert_header
5219 (compose, buf, sizeof(buf), str,
5220 strlen("Resent-Cc") + 2, TRUE);
5222 if (first_cc_address) {
5223 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5224 first_cc_address = FALSE;
5226 err |= (fprintf(fp, ",") < 0);
5228 err |= (fprintf(fp, "%s", buf) < 0);
5232 if (!first_cc_address) {
5233 err |= (fprintf(fp, "\n") < 0);
5236 return (err ? -1:0);
5239 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5241 gchar buf[BUFFSIZE];
5243 const gchar *entstr;
5244 /* struct utsname utsbuf; */
5245 gboolean err = FALSE;
5247 cm_return_val_if_fail(fp != NULL, -1);
5248 cm_return_val_if_fail(compose->account != NULL, -1);
5249 cm_return_val_if_fail(compose->account->address != NULL, -1);
5252 get_rfc822_date(buf, sizeof(buf));
5253 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5256 if (compose->account->name && *compose->account->name) {
5257 compose_convert_header
5258 (compose, buf, sizeof(buf), compose->account->name,
5259 strlen("From: "), TRUE);
5260 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5261 buf, compose->account->address) < 0);
5263 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5266 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5267 if (*entstr != '\0') {
5268 Xstrdup_a(str, entstr, return -1);
5271 compose_convert_header(compose, buf, sizeof(buf), str,
5272 strlen("Subject: "), FALSE);
5273 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5277 /* Resent-Message-ID */
5278 if (compose->account->set_domain && compose->account->domain) {
5279 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5280 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5281 g_snprintf(buf, sizeof(buf), "%s",
5282 strchr(compose->account->address, '@') ?
5283 strchr(compose->account->address, '@')+1 :
5284 compose->account->address);
5286 g_snprintf(buf, sizeof(buf), "%s", "");
5289 if (compose->account->gen_msgid) {
5291 if (compose->account->msgid_with_addr) {
5292 addr = compose->account->address;
5294 generate_msgid(buf, sizeof(buf), addr);
5295 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5296 compose->msgid = g_strdup(buf);
5298 compose->msgid = NULL;
5301 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5304 /* separator between header and body */
5305 err |= (fputs("\n", fp) == EOF);
5307 return (err ? -1:0);
5310 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5314 gchar buf[BUFFSIZE];
5316 gboolean skip = FALSE;
5317 gboolean err = FALSE;
5318 gchar *not_included[]={
5319 "Return-Path:", "Delivered-To:", "Received:",
5320 "Subject:", "X-UIDL:", "AF:",
5321 "NF:", "PS:", "SRH:",
5322 "SFN:", "DSR:", "MID:",
5323 "CFG:", "PT:", "S:",
5324 "RQ:", "SSV:", "NSV:",
5325 "SSH:", "R:", "MAID:",
5326 "NAID:", "RMID:", "FMID:",
5327 "SCF:", "RRCPT:", "NG:",
5328 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5329 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5330 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5331 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5332 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5335 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5336 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5340 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5342 for (i = 0; not_included[i] != NULL; i++) {
5343 if (g_ascii_strncasecmp(buf, not_included[i],
5344 strlen(not_included[i])) == 0) {
5351 if (fputs(buf, fdest) == -1)
5354 if (!prefs_common.redirect_keep_from) {
5355 if (g_ascii_strncasecmp(buf, "From:",
5356 strlen("From:")) == 0) {
5357 err |= (fputs(" (by way of ", fdest) == EOF);
5358 if (compose->account->name
5359 && *compose->account->name) {
5360 compose_convert_header
5361 (compose, buf, sizeof(buf),
5362 compose->account->name,
5365 err |= (fprintf(fdest, "%s <%s>",
5367 compose->account->address) < 0);
5369 err |= (fprintf(fdest, "%s",
5370 compose->account->address) < 0);
5371 err |= (fputs(")", fdest) == EOF);
5375 if (fputs("\n", fdest) == -1)
5382 if (compose_redirect_write_headers(compose, fdest))
5385 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5386 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5399 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5401 GtkTextBuffer *buffer;
5402 GtkTextIter start, end;
5405 const gchar *out_codeset;
5406 EncodingType encoding = ENC_UNKNOWN;
5407 MimeInfo *mimemsg, *mimetext;
5409 const gchar *src_codeset = CS_INTERNAL;
5410 gchar *from_addr = NULL;
5411 gchar *from_name = NULL;
5413 if (action == COMPOSE_WRITE_FOR_SEND)
5414 attach_parts = TRUE;
5416 /* create message MimeInfo */
5417 mimemsg = procmime_mimeinfo_new();
5418 mimemsg->type = MIMETYPE_MESSAGE;
5419 mimemsg->subtype = g_strdup("rfc822");
5420 mimemsg->content = MIMECONTENT_MEM;
5421 mimemsg->tmp = TRUE; /* must free content later */
5422 mimemsg->data.mem = compose_get_header(compose);
5424 /* Create text part MimeInfo */
5425 /* get all composed text */
5426 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5427 gtk_text_buffer_get_start_iter(buffer, &start);
5428 gtk_text_buffer_get_end_iter(buffer, &end);
5429 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5431 out_codeset = conv_get_charset_str(compose->out_encoding);
5433 if (!out_codeset && is_ascii_str(chars)) {
5434 out_codeset = CS_US_ASCII;
5435 } else if (prefs_common.outgoing_fallback_to_ascii &&
5436 is_ascii_str(chars)) {
5437 out_codeset = CS_US_ASCII;
5438 encoding = ENC_7BIT;
5442 gchar *test_conv_global_out = NULL;
5443 gchar *test_conv_reply = NULL;
5445 /* automatic mode. be automatic. */
5446 codeconv_set_strict(TRUE);
5448 out_codeset = conv_get_outgoing_charset_str();
5450 debug_print("trying to convert to %s\n", out_codeset);
5451 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5454 if (!test_conv_global_out && compose->orig_charset
5455 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5456 out_codeset = compose->orig_charset;
5457 debug_print("failure; trying to convert to %s\n", out_codeset);
5458 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5461 if (!test_conv_global_out && !test_conv_reply) {
5463 out_codeset = CS_INTERNAL;
5464 debug_print("failure; finally using %s\n", out_codeset);
5466 g_free(test_conv_global_out);
5467 g_free(test_conv_reply);
5468 codeconv_set_strict(FALSE);
5471 if (encoding == ENC_UNKNOWN) {
5472 if (prefs_common.encoding_method == CTE_BASE64)
5473 encoding = ENC_BASE64;
5474 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5475 encoding = ENC_QUOTED_PRINTABLE;
5476 else if (prefs_common.encoding_method == CTE_8BIT)
5477 encoding = ENC_8BIT;
5479 encoding = procmime_get_encoding_for_charset(out_codeset);
5482 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5483 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5485 if (action == COMPOSE_WRITE_FOR_SEND) {
5486 codeconv_set_strict(TRUE);
5487 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5488 codeconv_set_strict(FALSE);
5494 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5495 "to the specified %s charset.\n"
5496 "Send it as %s?"), out_codeset, src_codeset);
5497 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5498 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5501 if (aval != G_ALERTALTERNATE) {
5506 out_codeset = src_codeset;
5512 out_codeset = src_codeset;
5517 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5518 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5519 strstr(buf, "\nFrom ") != NULL) {
5520 encoding = ENC_QUOTED_PRINTABLE;
5524 mimetext = procmime_mimeinfo_new();
5525 mimetext->content = MIMECONTENT_MEM;
5526 mimetext->tmp = TRUE; /* must free content later */
5527 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5528 * and free the data, which we need later. */
5529 mimetext->data.mem = g_strdup(buf);
5530 mimetext->type = MIMETYPE_TEXT;
5531 mimetext->subtype = g_strdup("plain");
5532 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5533 g_strdup(out_codeset));
5535 /* protect trailing spaces when signing message */
5536 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5537 privacy_system_can_sign(compose->privacy_system)) {
5538 encoding = ENC_QUOTED_PRINTABLE;
5541 debug_print("main text: %zd bytes encoded as %s in %d\n",
5542 strlen(buf), out_codeset, encoding);
5544 /* check for line length limit */
5545 if (action == COMPOSE_WRITE_FOR_SEND &&
5546 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5547 check_line_length(buf, 1000, &line) < 0) {
5551 msg = g_strdup_printf
5552 (_("Line %d exceeds the line length limit (998 bytes).\n"
5553 "The contents of the message might be broken on the way to the delivery.\n"
5555 "Send it anyway?"), line + 1);
5556 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5558 if (aval != G_ALERTALTERNATE) {
5564 if (encoding != ENC_UNKNOWN)
5565 procmime_encode_content(mimetext, encoding);
5567 /* append attachment parts */
5568 if (compose_use_attach(compose) && attach_parts) {
5569 MimeInfo *mimempart;
5570 gchar *boundary = NULL;
5571 mimempart = procmime_mimeinfo_new();
5572 mimempart->content = MIMECONTENT_EMPTY;
5573 mimempart->type = MIMETYPE_MULTIPART;
5574 mimempart->subtype = g_strdup("mixed");
5578 boundary = generate_mime_boundary(NULL);
5579 } while (strstr(buf, boundary) != NULL);
5581 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5584 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5586 g_node_append(mimempart->node, mimetext->node);
5587 g_node_append(mimemsg->node, mimempart->node);
5589 if (compose_add_attachments(compose, mimempart) < 0)
5592 g_node_append(mimemsg->node, mimetext->node);
5596 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5597 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5598 /* extract name and address */
5599 if (strstr(spec, " <") && strstr(spec, ">")) {
5600 from_addr = g_strdup(strrchr(spec, '<')+1);
5601 *(strrchr(from_addr, '>')) = '\0';
5602 from_name = g_strdup(spec);
5603 *(strrchr(from_name, '<')) = '\0';
5610 /* sign message if sending */
5611 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5612 privacy_system_can_sign(compose->privacy_system))
5613 if (!privacy_sign(compose->privacy_system, mimemsg,
5614 compose->account, from_addr)) {
5621 procmime_write_mimeinfo(mimemsg, fp);
5623 procmime_mimeinfo_free_all(mimemsg);
5628 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5630 GtkTextBuffer *buffer;
5631 GtkTextIter start, end;
5636 if ((fp = g_fopen(file, "wb")) == NULL) {
5637 FILE_OP_ERROR(file, "fopen");
5641 /* chmod for security */
5642 if (change_file_mode_rw(fp, file) < 0) {
5643 FILE_OP_ERROR(file, "chmod");
5644 g_warning("can't change file mode\n");
5647 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5648 gtk_text_buffer_get_start_iter(buffer, &start);
5649 gtk_text_buffer_get_end_iter(buffer, &end);
5650 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5652 chars = conv_codeset_strdup
5653 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5656 if (!chars) return -1;
5659 len = strlen(chars);
5660 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5661 FILE_OP_ERROR(file, "fwrite");
5670 if (fclose(fp) == EOF) {
5671 FILE_OP_ERROR(file, "fclose");
5678 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5681 MsgInfo *msginfo = compose->targetinfo;
5683 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5684 if (!msginfo) return -1;
5686 if (!force && MSG_IS_LOCKED(msginfo->flags))
5689 item = msginfo->folder;
5690 cm_return_val_if_fail(item != NULL, -1);
5692 if (procmsg_msg_exist(msginfo) &&
5693 (folder_has_parent_of_type(item, F_QUEUE) ||
5694 folder_has_parent_of_type(item, F_DRAFT)
5695 || msginfo == compose->autosaved_draft)) {
5696 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5697 g_warning("can't remove the old message\n");
5700 debug_print("removed reedit target %d\n", msginfo->msgnum);
5707 static void compose_remove_draft(Compose *compose)
5710 MsgInfo *msginfo = compose->targetinfo;
5711 drafts = account_get_special_folder(compose->account, F_DRAFT);
5713 if (procmsg_msg_exist(msginfo)) {
5714 folder_item_remove_msg(drafts, msginfo->msgnum);
5719 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5720 gboolean remove_reedit_target)
5722 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5725 static gboolean compose_warn_encryption(Compose *compose)
5727 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5728 AlertValue val = G_ALERTALTERNATE;
5730 if (warning == NULL)
5733 val = alertpanel_full(_("Encryption warning"), warning,
5734 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5735 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5736 if (val & G_ALERTDISABLE) {
5737 val &= ~G_ALERTDISABLE;
5738 if (val == G_ALERTALTERNATE)
5739 privacy_inhibit_encrypt_warning(compose->privacy_system,
5743 if (val == G_ALERTALTERNATE) {
5750 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5751 gchar **msgpath, gboolean check_subject,
5752 gboolean remove_reedit_target)
5759 PrefsAccount *mailac = NULL, *newsac = NULL;
5760 gboolean err = FALSE;
5762 debug_print("queueing message...\n");
5763 cm_return_val_if_fail(compose->account != NULL, -1);
5765 if (compose_check_entries(compose, check_subject) == FALSE) {
5766 if (compose->batch) {
5767 gtk_widget_show_all(compose->window);
5772 if (!compose->to_list && !compose->newsgroup_list) {
5773 g_warning("can't get recipient list.");
5777 if (compose->to_list) {
5778 if (compose->account->protocol != A_NNTP)
5779 mailac = compose->account;
5780 else if (cur_account && cur_account->protocol != A_NNTP)
5781 mailac = cur_account;
5782 else if (!(mailac = compose_current_mail_account())) {
5783 alertpanel_error(_("No account for sending mails available!"));
5788 if (compose->newsgroup_list) {
5789 if (compose->account->protocol == A_NNTP)
5790 newsac = compose->account;
5792 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5797 /* write queue header */
5798 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5799 G_DIR_SEPARATOR, compose, (guint) rand());
5800 debug_print("queuing to %s\n", tmp);
5801 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5802 FILE_OP_ERROR(tmp, "fopen");
5807 if (change_file_mode_rw(fp, tmp) < 0) {
5808 FILE_OP_ERROR(tmp, "chmod");
5809 g_warning("can't change file mode\n");
5812 /* queueing variables */
5813 err |= (fprintf(fp, "AF:\n") < 0);
5814 err |= (fprintf(fp, "NF:0\n") < 0);
5815 err |= (fprintf(fp, "PS:10\n") < 0);
5816 err |= (fprintf(fp, "SRH:1\n") < 0);
5817 err |= (fprintf(fp, "SFN:\n") < 0);
5818 err |= (fprintf(fp, "DSR:\n") < 0);
5820 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5822 err |= (fprintf(fp, "MID:\n") < 0);
5823 err |= (fprintf(fp, "CFG:\n") < 0);
5824 err |= (fprintf(fp, "PT:0\n") < 0);
5825 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5826 err |= (fprintf(fp, "RQ:\n") < 0);
5828 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5830 err |= (fprintf(fp, "SSV:\n") < 0);
5832 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5834 err |= (fprintf(fp, "NSV:\n") < 0);
5835 err |= (fprintf(fp, "SSH:\n") < 0);
5836 /* write recepient list */
5837 if (compose->to_list) {
5838 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5839 for (cur = compose->to_list->next; cur != NULL;
5841 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5842 err |= (fprintf(fp, "\n") < 0);
5844 /* write newsgroup list */
5845 if (compose->newsgroup_list) {
5846 err |= (fprintf(fp, "NG:") < 0);
5847 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5848 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5849 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5850 err |= (fprintf(fp, "\n") < 0);
5852 /* Sylpheed account IDs */
5854 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5856 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5859 if (compose->privacy_system != NULL) {
5860 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5861 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5862 if (compose->use_encryption) {
5864 if (!compose_warn_encryption(compose)) {
5870 if (mailac && mailac->encrypt_to_self) {
5871 GSList *tmp_list = g_slist_copy(compose->to_list);
5872 tmp_list = g_slist_append(tmp_list, compose->account->address);
5873 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5874 g_slist_free(tmp_list);
5876 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5878 if (encdata != NULL) {
5879 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5880 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5881 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5883 } /* else we finally dont want to encrypt */
5885 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5886 /* and if encdata was null, it means there's been a problem in
5889 g_warning("failed to write queue message");
5899 /* Save copy folder */
5900 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5901 gchar *savefolderid;
5903 savefolderid = compose_get_save_to(compose);
5904 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5905 g_free(savefolderid);
5907 /* Save copy folder */
5908 if (compose->return_receipt) {
5909 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5911 /* Message-ID of message replying to */
5912 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5915 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5916 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5919 /* Message-ID of message forwarding to */
5920 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5923 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5924 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5928 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5929 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5931 /* end of headers */
5932 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5934 if (compose->redirect_filename != NULL) {
5935 if (compose_redirect_write_to_file(compose, fp) < 0) {
5943 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5947 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5951 g_warning("failed to write queue message\n");
5957 if (fclose(fp) == EOF) {
5958 FILE_OP_ERROR(tmp, "fclose");
5964 if (item && *item) {
5967 queue = account_get_special_folder(compose->account, F_QUEUE);
5970 g_warning("can't find queue folder\n");
5975 folder_item_scan(queue);
5976 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5977 g_warning("can't queue the message\n");
5983 if (msgpath == NULL) {
5989 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5990 compose_remove_reedit_target(compose, FALSE);
5993 if ((msgnum != NULL) && (item != NULL)) {
6001 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6004 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6006 struct stat statbuf;
6007 gchar *type, *subtype;
6008 GtkTreeModel *model;
6011 model = gtk_tree_view_get_model(tree_view);
6013 if (!gtk_tree_model_get_iter_first(model, &iter))
6016 gtk_tree_model_get(model, &iter,
6020 if (!is_file_exist(ainfo->file)) {
6021 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6022 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6023 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6025 if (val == G_ALERTDEFAULT) {
6030 mimepart = procmime_mimeinfo_new();
6031 mimepart->content = MIMECONTENT_FILE;
6032 mimepart->data.filename = g_strdup(ainfo->file);
6033 mimepart->tmp = FALSE; /* or we destroy our attachment */
6034 mimepart->offset = 0;
6036 g_stat(ainfo->file, &statbuf);
6037 mimepart->length = statbuf.st_size;
6039 type = g_strdup(ainfo->content_type);
6041 if (!strchr(type, '/')) {
6043 type = g_strdup("application/octet-stream");
6046 subtype = strchr(type, '/') + 1;
6047 *(subtype - 1) = '\0';
6048 mimepart->type = procmime_get_media_type(type);
6049 mimepart->subtype = g_strdup(subtype);
6052 if (mimepart->type == MIMETYPE_MESSAGE &&
6053 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6054 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6055 } else if (mimepart->type == MIMETYPE_TEXT) {
6056 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6057 /* Text parts with no name come from multipart/alternative
6058 * forwards. Make sure the recipient won't look at the
6059 * original HTML part by mistake. */
6060 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6061 ainfo->name = g_strdup_printf(_("Original %s part"),
6065 g_hash_table_insert(mimepart->typeparameters,
6066 g_strdup("charset"), g_strdup(ainfo->charset));
6068 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6069 if (mimepart->type == MIMETYPE_APPLICATION &&
6070 !strcmp2(mimepart->subtype, "octet-stream"))
6071 g_hash_table_insert(mimepart->typeparameters,
6072 g_strdup("name"), g_strdup(ainfo->name));
6073 g_hash_table_insert(mimepart->dispositionparameters,
6074 g_strdup("filename"), g_strdup(ainfo->name));
6075 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6078 if (mimepart->type == MIMETYPE_MESSAGE
6079 || mimepart->type == MIMETYPE_MULTIPART)
6080 ainfo->encoding = ENC_BINARY;
6081 else if (compose->use_signing) {
6082 if (ainfo->encoding == ENC_7BIT)
6083 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6084 else if (ainfo->encoding == ENC_8BIT)
6085 ainfo->encoding = ENC_BASE64;
6090 procmime_encode_content(mimepart, ainfo->encoding);
6092 g_node_append(parent->node, mimepart->node);
6093 } while (gtk_tree_model_iter_next(model, &iter));
6098 #define IS_IN_CUSTOM_HEADER(header) \
6099 (compose->account->add_customhdr && \
6100 custom_header_find(compose->account->customhdr_list, header) != NULL)
6102 static void compose_add_headerfield_from_headerlist(Compose *compose,
6104 const gchar *fieldname,
6105 const gchar *seperator)
6107 gchar *str, *fieldname_w_colon;
6108 gboolean add_field = FALSE;
6110 ComposeHeaderEntry *headerentry;
6111 const gchar *headerentryname;
6112 const gchar *trans_fieldname;
6115 if (IS_IN_CUSTOM_HEADER(fieldname))
6118 debug_print("Adding %s-fields\n", fieldname);
6120 fieldstr = g_string_sized_new(64);
6122 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6123 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6125 for (list = compose->header_list; list; list = list->next) {
6126 headerentry = ((ComposeHeaderEntry *)list->data);
6127 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6129 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6130 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6132 if (str[0] != '\0') {
6134 g_string_append(fieldstr, seperator);
6135 g_string_append(fieldstr, str);
6144 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6145 compose_convert_header
6146 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6147 strlen(fieldname) + 2, TRUE);
6148 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6152 g_free(fieldname_w_colon);
6153 g_string_free(fieldstr, TRUE);
6158 static gchar *compose_get_manual_headers_info(Compose *compose)
6160 GString *sh_header = g_string_new(" ");
6162 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6164 for (list = compose->header_list; list; list = list->next) {
6165 ComposeHeaderEntry *headerentry;
6168 gchar *headername_wcolon;
6169 const gchar *headername_trans;
6171 gboolean standard_header = FALSE;
6173 headerentry = ((ComposeHeaderEntry *)list->data);
6175 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6177 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6182 if (!strstr(tmp, ":")) {
6183 headername_wcolon = g_strconcat(tmp, ":", NULL);
6184 headername = g_strdup(tmp);
6186 headername_wcolon = g_strdup(tmp);
6187 headername = g_strdup(strtok(tmp, ":"));
6191 string = std_headers;
6192 while (*string != NULL) {
6193 headername_trans = prefs_common_translated_header_name(*string);
6194 if (!strcmp(headername_trans, headername_wcolon))
6195 standard_header = TRUE;
6198 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6199 g_string_append_printf(sh_header, "%s ", headername);
6201 g_free(headername_wcolon);
6203 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6204 return g_string_free(sh_header, FALSE);
6207 static gchar *compose_get_header(Compose *compose)
6209 gchar buf[BUFFSIZE];
6210 const gchar *entry_str;
6214 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6216 gchar *from_name = NULL, *from_address = NULL;
6219 cm_return_val_if_fail(compose->account != NULL, NULL);
6220 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6222 header = g_string_sized_new(64);
6225 get_rfc822_date(buf, sizeof(buf));
6226 g_string_append_printf(header, "Date: %s\n", buf);
6230 if (compose->account->name && *compose->account->name) {
6232 QUOTE_IF_REQUIRED(buf, compose->account->name);
6233 tmp = g_strdup_printf("%s <%s>",
6234 buf, compose->account->address);
6236 tmp = g_strdup_printf("%s",
6237 compose->account->address);
6239 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6240 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6242 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6243 from_address = g_strdup(compose->account->address);
6245 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6246 /* extract name and address */
6247 if (strstr(spec, " <") && strstr(spec, ">")) {
6248 from_address = g_strdup(strrchr(spec, '<')+1);
6249 *(strrchr(from_address, '>')) = '\0';
6250 from_name = g_strdup(spec);
6251 *(strrchr(from_name, '<')) = '\0';
6254 from_address = g_strdup(spec);
6261 if (from_name && *from_name) {
6262 compose_convert_header
6263 (compose, buf, sizeof(buf), from_name,
6264 strlen("From: "), TRUE);
6265 QUOTE_IF_REQUIRED(name, buf);
6267 g_string_append_printf(header, "From: %s <%s>\n",
6268 name, from_address);
6270 g_string_append_printf(header, "From: %s\n", from_address);
6273 g_free(from_address);
6276 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6279 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6282 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6286 * If this account is a NNTP account remove Bcc header from
6287 * message body since it otherwise will be publicly shown
6289 if (compose->account->protocol != A_NNTP)
6290 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6293 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6295 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6298 compose_convert_header(compose, buf, sizeof(buf), str,
6299 strlen("Subject: "), FALSE);
6300 g_string_append_printf(header, "Subject: %s\n", buf);
6306 if (compose->account->set_domain && compose->account->domain) {
6307 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6308 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6309 g_snprintf(buf, sizeof(buf), "%s",
6310 strchr(compose->account->address, '@') ?
6311 strchr(compose->account->address, '@')+1 :
6312 compose->account->address);
6314 g_snprintf(buf, sizeof(buf), "%s", "");
6317 if (compose->account->gen_msgid) {
6319 if (compose->account->msgid_with_addr) {
6320 addr = compose->account->address;
6322 generate_msgid(buf, sizeof(buf), addr);
6323 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6324 compose->msgid = g_strdup(buf);
6326 compose->msgid = NULL;
6329 if (compose->remove_references == FALSE) {
6331 if (compose->inreplyto && compose->to_list)
6332 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6335 if (compose->references)
6336 g_string_append_printf(header, "References: %s\n", compose->references);
6340 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6343 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6346 if (compose->account->organization &&
6347 strlen(compose->account->organization) &&
6348 !IS_IN_CUSTOM_HEADER("Organization")) {
6349 compose_convert_header(compose, buf, sizeof(buf),
6350 compose->account->organization,
6351 strlen("Organization: "), FALSE);
6352 g_string_append_printf(header, "Organization: %s\n", buf);
6355 /* Program version and system info */
6356 if (compose->account->gen_xmailer &&
6357 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6358 !compose->newsgroup_list) {
6359 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6361 gtk_major_version, gtk_minor_version, gtk_micro_version,
6364 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6365 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6367 gtk_major_version, gtk_minor_version, gtk_micro_version,
6371 /* custom headers */
6372 if (compose->account->add_customhdr) {
6375 for (cur = compose->account->customhdr_list; cur != NULL;
6377 CustomHeader *chdr = (CustomHeader *)cur->data;
6379 if (custom_header_is_allowed(chdr->name)
6380 && chdr->value != NULL
6381 && *(chdr->value) != '\0') {
6382 compose_convert_header
6383 (compose, buf, sizeof(buf),
6385 strlen(chdr->name) + 2, FALSE);
6386 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6391 /* Automatic Faces and X-Faces */
6392 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6393 g_string_append_printf(header, "X-Face: %s\n", buf);
6395 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6396 g_string_append_printf(header, "X-Face: %s\n", buf);
6398 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6399 g_string_append_printf(header, "Face: %s\n", buf);
6401 else if (get_default_face (buf, sizeof(buf)) == 0) {
6402 g_string_append_printf(header, "Face: %s\n", buf);
6406 switch (compose->priority) {
6407 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6408 "X-Priority: 1 (Highest)\n");
6410 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6411 "X-Priority: 2 (High)\n");
6413 case PRIORITY_NORMAL: break;
6414 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6415 "X-Priority: 4 (Low)\n");
6417 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6418 "X-Priority: 5 (Lowest)\n");
6420 default: debug_print("compose: priority unknown : %d\n",
6424 /* Request Return Receipt */
6425 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6426 if (compose->return_receipt) {
6427 if (compose->account->name
6428 && *compose->account->name) {
6429 compose_convert_header(compose, buf, sizeof(buf),
6430 compose->account->name,
6431 strlen("Disposition-Notification-To: "),
6433 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6435 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6439 /* get special headers */
6440 for (list = compose->header_list; list; list = list->next) {
6441 ComposeHeaderEntry *headerentry;
6444 gchar *headername_wcolon;
6445 const gchar *headername_trans;
6448 gboolean standard_header = FALSE;
6450 headerentry = ((ComposeHeaderEntry *)list->data);
6452 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6454 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6459 if (!strstr(tmp, ":")) {
6460 headername_wcolon = g_strconcat(tmp, ":", NULL);
6461 headername = g_strdup(tmp);
6463 headername_wcolon = g_strdup(tmp);
6464 headername = g_strdup(strtok(tmp, ":"));
6468 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6469 Xstrdup_a(headervalue, entry_str, return NULL);
6470 subst_char(headervalue, '\r', ' ');
6471 subst_char(headervalue, '\n', ' ');
6472 string = std_headers;
6473 while (*string != NULL) {
6474 headername_trans = prefs_common_translated_header_name(*string);
6475 if (!strcmp(headername_trans, headername_wcolon))
6476 standard_header = TRUE;
6479 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6480 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6483 g_free(headername_wcolon);
6487 g_string_free(header, FALSE);
6492 #undef IS_IN_CUSTOM_HEADER
6494 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6495 gint header_len, gboolean addr_field)
6497 gchar *tmpstr = NULL;
6498 const gchar *out_codeset = NULL;
6500 cm_return_if_fail(src != NULL);
6501 cm_return_if_fail(dest != NULL);
6503 if (len < 1) return;
6505 tmpstr = g_strdup(src);
6507 subst_char(tmpstr, '\n', ' ');
6508 subst_char(tmpstr, '\r', ' ');
6511 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6512 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6513 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6518 codeconv_set_strict(TRUE);
6519 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6520 conv_get_charset_str(compose->out_encoding));
6521 codeconv_set_strict(FALSE);
6523 if (!dest || *dest == '\0') {
6524 gchar *test_conv_global_out = NULL;
6525 gchar *test_conv_reply = NULL;
6527 /* automatic mode. be automatic. */
6528 codeconv_set_strict(TRUE);
6530 out_codeset = conv_get_outgoing_charset_str();
6532 debug_print("trying to convert to %s\n", out_codeset);
6533 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6536 if (!test_conv_global_out && compose->orig_charset
6537 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6538 out_codeset = compose->orig_charset;
6539 debug_print("failure; trying to convert to %s\n", out_codeset);
6540 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6543 if (!test_conv_global_out && !test_conv_reply) {
6545 out_codeset = CS_INTERNAL;
6546 debug_print("finally using %s\n", out_codeset);
6548 g_free(test_conv_global_out);
6549 g_free(test_conv_reply);
6550 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6552 codeconv_set_strict(FALSE);
6557 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6561 cm_return_if_fail(user_data != NULL);
6563 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6564 g_strstrip(address);
6565 if (*address != '\0') {
6566 gchar *name = procheader_get_fromname(address);
6567 extract_address(address);
6568 #ifndef USE_NEW_ADDRBOOK
6569 addressbook_add_contact(name, address, NULL, NULL);
6571 debug_print("%s: %s\n", name, address);
6572 if (addressadd_selection(name, address, NULL, NULL)) {
6573 debug_print( "addressbook_add_contact - added\n" );
6580 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6582 GtkWidget *menuitem;
6585 cm_return_if_fail(menu != NULL);
6586 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6588 menuitem = gtk_separator_menu_item_new();
6589 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6590 gtk_widget_show(menuitem);
6592 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6593 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6595 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6596 g_strstrip(address);
6597 if (*address == '\0') {
6598 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6601 g_signal_connect(G_OBJECT(menuitem), "activate",
6602 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6603 gtk_widget_show(menuitem);
6606 void compose_add_extra_header(gchar *header, GtkListStore *model)
6609 if (strcmp(header, "")) {
6610 COMBOBOX_ADD(model, header, COMPOSE_TO);
6614 void compose_add_extra_header_entries(GtkListStore *model)
6618 gchar buf[BUFFSIZE];
6621 if (extra_headers == NULL) {
6622 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6623 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6624 debug_print("extra headers file not found\n");
6625 goto extra_headers_done;
6627 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6628 lastc = strlen(buf) - 1; /* remove trailing \n */
6629 buf[lastc] = (buf[lastc] == '\n')? '\0': buf[lastc];
6631 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6632 buf[lastc] = '\0'; /* remove trailing : for comparison */
6633 if (custom_header_is_allowed(buf)) {
6635 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6638 g_message("disallowed extra header line: %s\n", buf);
6642 g_message("invalid extra header line: %s\n", buf);
6648 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6649 extra_headers = g_slist_reverse(extra_headers);
6651 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6654 static void compose_create_header_entry(Compose *compose)
6656 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6663 const gchar *header = NULL;
6664 ComposeHeaderEntry *headerentry;
6665 gboolean standard_header = FALSE;
6666 GtkListStore *model;
6668 #if !(GTK_CHECK_VERSION(2,12,0))
6669 GtkTooltips *tips = compose->tooltips;
6672 headerentry = g_new0(ComposeHeaderEntry, 1);
6674 /* Combo box model */
6675 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6676 #if !GTK_CHECK_VERSION(2, 24, 0)
6677 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6679 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6681 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6683 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6685 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6686 COMPOSE_NEWSGROUPS);
6687 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6689 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6690 COMPOSE_FOLLOWUPTO);
6691 compose_add_extra_header_entries(model);
6694 #if GTK_CHECK_VERSION(2, 24, 0)
6695 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6696 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6697 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6698 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6699 gtk_combo_box_set_entry_text_column(combo, 0);
6701 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6702 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6703 G_CALLBACK(compose_grab_focus_cb), compose);
6704 gtk_widget_show(combo);
6707 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6708 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6711 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6712 compose->header_nextrow, compose->header_nextrow+1,
6713 GTK_SHRINK, GTK_FILL, 0, 0);
6714 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6715 const gchar *last_header_entry = gtk_entry_get_text(
6716 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6718 while (*string != NULL) {
6719 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6720 standard_header = TRUE;
6723 if (standard_header)
6724 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6726 if (!compose->header_last || !standard_header) {
6727 switch(compose->account->protocol) {
6729 header = prefs_common_translated_header_name("Newsgroups:");
6732 header = prefs_common_translated_header_name("To:");
6737 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6739 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6740 G_CALLBACK(compose_grab_focus_cb), compose);
6742 /* Entry field with cleanup button */
6743 button = gtk_button_new();
6744 gtk_button_set_image(GTK_BUTTON(button),
6745 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6746 gtk_widget_show(button);
6747 CLAWS_SET_TIP(button,
6748 _("Delete entry contents"));
6749 entry = gtk_entry_new();
6750 gtk_widget_show(entry);
6751 CLAWS_SET_TIP(entry,
6752 _("Use <tab> to autocomplete from addressbook"));
6753 hbox = gtk_hbox_new (FALSE, 0);
6754 gtk_widget_show(hbox);
6755 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6756 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6757 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6758 compose->header_nextrow, compose->header_nextrow+1,
6759 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6761 g_signal_connect(G_OBJECT(entry), "key-press-event",
6762 G_CALLBACK(compose_headerentry_key_press_event_cb),
6764 g_signal_connect(G_OBJECT(entry), "changed",
6765 G_CALLBACK(compose_headerentry_changed_cb),
6767 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6768 G_CALLBACK(compose_grab_focus_cb), compose);
6770 g_signal_connect(G_OBJECT(button), "clicked",
6771 G_CALLBACK(compose_headerentry_button_clicked_cb),
6775 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6776 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6777 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6778 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6779 G_CALLBACK(compose_header_drag_received_cb),
6781 g_signal_connect(G_OBJECT(entry), "drag-drop",
6782 G_CALLBACK(compose_drag_drop),
6784 g_signal_connect(G_OBJECT(entry), "populate-popup",
6785 G_CALLBACK(compose_entry_popup_extend),
6788 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6790 headerentry->compose = compose;
6791 headerentry->combo = combo;
6792 headerentry->entry = entry;
6793 headerentry->button = button;
6794 headerentry->hbox = hbox;
6795 headerentry->headernum = compose->header_nextrow;
6796 headerentry->type = PREF_NONE;
6798 compose->header_nextrow++;
6799 compose->header_last = headerentry;
6800 compose->header_list =
6801 g_slist_append(compose->header_list,
6805 static void compose_add_header_entry(Compose *compose, const gchar *header,
6806 gchar *text, ComposePrefType pref_type)
6808 ComposeHeaderEntry *last_header = compose->header_last;
6809 gchar *tmp = g_strdup(text), *email;
6810 gboolean replyto_hdr;
6812 replyto_hdr = (!strcasecmp(header,
6813 prefs_common_translated_header_name("Reply-To:")) ||
6815 prefs_common_translated_header_name("Followup-To:")) ||
6817 prefs_common_translated_header_name("In-Reply-To:")));
6819 extract_address(tmp);
6820 email = g_utf8_strdown(tmp, -1);
6822 if (replyto_hdr == FALSE &&
6823 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6825 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6826 header, text, (gint) pref_type);
6832 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6833 gtk_entry_set_text(GTK_ENTRY(
6834 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6836 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6837 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6838 last_header->type = pref_type;
6840 if (replyto_hdr == FALSE)
6841 g_hash_table_insert(compose->email_hashtable, email,
6842 GUINT_TO_POINTER(1));
6849 static void compose_destroy_headerentry(Compose *compose,
6850 ComposeHeaderEntry *headerentry)
6852 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6855 extract_address(text);
6856 email = g_utf8_strdown(text, -1);
6857 g_hash_table_remove(compose->email_hashtable, email);
6861 gtk_widget_destroy(headerentry->combo);
6862 gtk_widget_destroy(headerentry->entry);
6863 gtk_widget_destroy(headerentry->button);
6864 gtk_widget_destroy(headerentry->hbox);
6865 g_free(headerentry);
6868 static void compose_remove_header_entries(Compose *compose)
6871 for (list = compose->header_list; list; list = list->next)
6872 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6874 compose->header_last = NULL;
6875 g_slist_free(compose->header_list);
6876 compose->header_list = NULL;
6877 compose->header_nextrow = 1;
6878 compose_create_header_entry(compose);
6881 static GtkWidget *compose_create_header(Compose *compose)
6883 GtkWidget *from_optmenu_hbox;
6884 GtkWidget *header_scrolledwin_main;
6885 GtkWidget *header_table_main;
6886 GtkWidget *header_scrolledwin;
6887 GtkWidget *header_table;
6889 /* parent with account selection and from header */
6890 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6891 gtk_widget_show(header_scrolledwin_main);
6892 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6894 header_table_main = gtk_table_new(2, 2, FALSE);
6895 gtk_widget_show(header_table_main);
6896 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6897 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6898 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6900 from_optmenu_hbox = compose_account_option_menu_create(compose);
6901 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6902 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6904 /* child with header labels and entries */
6905 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6906 gtk_widget_show(header_scrolledwin);
6907 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6909 header_table = gtk_table_new(2, 2, FALSE);
6910 gtk_widget_show(header_table);
6911 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6912 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6913 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6915 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6916 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6918 compose->header_table = header_table;
6919 compose->header_list = NULL;
6920 compose->header_nextrow = 0;
6922 compose_create_header_entry(compose);
6924 compose->table = NULL;
6926 return header_scrolledwin_main;
6929 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6931 Compose *compose = (Compose *)data;
6932 GdkEventButton event;
6935 event.time = gtk_get_current_event_time();
6937 return attach_button_pressed(compose->attach_clist, &event, compose);
6940 static GtkWidget *compose_create_attach(Compose *compose)
6942 GtkWidget *attach_scrwin;
6943 GtkWidget *attach_clist;
6945 GtkListStore *store;
6946 GtkCellRenderer *renderer;
6947 GtkTreeViewColumn *column;
6948 GtkTreeSelection *selection;
6950 /* attachment list */
6951 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6952 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6953 GTK_POLICY_AUTOMATIC,
6954 GTK_POLICY_AUTOMATIC);
6955 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6957 store = gtk_list_store_new(N_ATTACH_COLS,
6963 G_TYPE_AUTO_POINTER,
6965 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6966 (GTK_TREE_MODEL(store)));
6967 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6968 g_object_unref(store);
6970 renderer = gtk_cell_renderer_text_new();
6971 column = gtk_tree_view_column_new_with_attributes
6972 (_("Mime type"), renderer, "text",
6973 COL_MIMETYPE, NULL);
6974 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6976 renderer = gtk_cell_renderer_text_new();
6977 column = gtk_tree_view_column_new_with_attributes
6978 (_("Size"), renderer, "text",
6980 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6982 renderer = gtk_cell_renderer_text_new();
6983 column = gtk_tree_view_column_new_with_attributes
6984 (_("Name"), renderer, "text",
6986 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6988 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6989 prefs_common.use_stripes_everywhere);
6990 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6991 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6993 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6994 G_CALLBACK(attach_selected), compose);
6995 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6996 G_CALLBACK(attach_button_pressed), compose);
6998 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6999 G_CALLBACK(popup_attach_button_pressed), compose);
7001 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
7002 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7003 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
7004 G_CALLBACK(popup_attach_button_pressed), compose);
7006 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7007 G_CALLBACK(attach_key_pressed), compose);
7010 gtk_drag_dest_set(attach_clist,
7011 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7012 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7013 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7014 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7015 G_CALLBACK(compose_attach_drag_received_cb),
7017 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7018 G_CALLBACK(compose_drag_drop),
7021 compose->attach_scrwin = attach_scrwin;
7022 compose->attach_clist = attach_clist;
7024 return attach_scrwin;
7027 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7028 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7030 static GtkWidget *compose_create_others(Compose *compose)
7033 GtkWidget *savemsg_checkbtn;
7034 GtkWidget *savemsg_combo;
7035 GtkWidget *savemsg_select;
7038 gchar *folderidentifier;
7040 /* Table for settings */
7041 table = gtk_table_new(3, 1, FALSE);
7042 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7043 gtk_widget_show(table);
7044 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7047 /* Save Message to folder */
7048 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7049 gtk_widget_show(savemsg_checkbtn);
7050 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7051 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7052 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7054 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7055 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7057 #if !GTK_CHECK_VERSION(2, 24, 0)
7058 savemsg_combo = gtk_combo_box_entry_new_text();
7060 savemsg_combo = gtk_combo_box_text_new_with_entry();
7062 compose->savemsg_checkbtn = savemsg_checkbtn;
7063 compose->savemsg_combo = savemsg_combo;
7064 gtk_widget_show(savemsg_combo);
7066 if (prefs_common.compose_save_to_history)
7067 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7068 prefs_common.compose_save_to_history);
7070 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7071 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7072 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7073 G_CALLBACK(compose_grab_focus_cb), compose);
7074 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7075 folderidentifier = folder_item_get_identifier(account_get_special_folder
7076 (compose->account, F_OUTBOX));
7077 compose_set_save_to(compose, folderidentifier);
7078 g_free(folderidentifier);
7081 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7082 gtk_widget_show(savemsg_select);
7083 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7084 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7085 G_CALLBACK(compose_savemsg_select_cb),
7091 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7093 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7094 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7097 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7102 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7105 path = folder_item_get_identifier(dest);
7107 compose_set_save_to(compose, path);
7111 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7112 GdkAtom clip, GtkTextIter *insert_place);
7115 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7119 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7121 if (event->button == 3) {
7123 GtkTextIter sel_start, sel_end;
7124 gboolean stuff_selected;
7126 /* move the cursor to allow GtkAspell to check the word
7127 * under the mouse */
7128 if (event->x && event->y) {
7129 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7130 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7132 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7135 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7136 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7139 stuff_selected = gtk_text_buffer_get_selection_bounds(
7141 &sel_start, &sel_end);
7143 gtk_text_buffer_place_cursor (buffer, &iter);
7144 /* reselect stuff */
7146 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7147 gtk_text_buffer_select_range(buffer,
7148 &sel_start, &sel_end);
7150 return FALSE; /* pass the event so that the right-click goes through */
7153 if (event->button == 2) {
7158 /* get the middle-click position to paste at the correct place */
7159 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7160 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7162 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7165 entry_paste_clipboard(compose, text,
7166 prefs_common.linewrap_pastes,
7167 GDK_SELECTION_PRIMARY, &iter);
7175 static void compose_spell_menu_changed(void *data)
7177 Compose *compose = (Compose *)data;
7179 GtkWidget *menuitem;
7180 GtkWidget *parent_item;
7181 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7184 if (compose->gtkaspell == NULL)
7187 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7188 "/Menu/Spelling/Options");
7190 /* setting the submenu removes /Spelling/Options from the factory
7191 * so we need to save it */
7193 if (parent_item == NULL) {
7194 parent_item = compose->aspell_options_menu;
7195 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7197 compose->aspell_options_menu = parent_item;
7199 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7201 spell_menu = g_slist_reverse(spell_menu);
7202 for (items = spell_menu;
7203 items; items = items->next) {
7204 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7205 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7206 gtk_widget_show(GTK_WIDGET(menuitem));
7208 g_slist_free(spell_menu);
7210 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7211 gtk_widget_show(parent_item);
7214 static void compose_dict_changed(void *data)
7216 Compose *compose = (Compose *) data;
7218 if(compose->gtkaspell &&
7219 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7222 gtkaspell_highlight_all(compose->gtkaspell);
7223 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7227 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7229 Compose *compose = (Compose *)data;
7230 GdkEventButton event;
7233 event.time = gtk_get_current_event_time();
7237 return text_clicked(compose->text, &event, compose);
7240 static gboolean compose_force_window_origin = TRUE;
7241 static Compose *compose_create(PrefsAccount *account,
7250 GtkWidget *handlebox;
7252 GtkWidget *notebook;
7254 GtkWidget *attach_hbox;
7255 GtkWidget *attach_lab1;
7256 GtkWidget *attach_lab2;
7261 GtkWidget *subject_hbox;
7262 GtkWidget *subject_frame;
7263 GtkWidget *subject_entry;
7267 GtkWidget *edit_vbox;
7268 GtkWidget *ruler_hbox;
7270 GtkWidget *scrolledwin;
7272 GtkTextBuffer *buffer;
7273 GtkClipboard *clipboard;
7275 UndoMain *undostruct;
7277 GtkWidget *popupmenu;
7278 GtkWidget *tmpl_menu;
7279 GtkActionGroup *action_group = NULL;
7282 GtkAspell * gtkaspell = NULL;
7285 static GdkGeometry geometry;
7287 cm_return_val_if_fail(account != NULL, NULL);
7289 debug_print("Creating compose window...\n");
7290 compose = g_new0(Compose, 1);
7292 compose->batch = batch;
7293 compose->account = account;
7294 compose->folder = folder;
7296 compose->mutex = cm_mutex_new();
7297 compose->set_cursor_pos = -1;
7299 #if !(GTK_CHECK_VERSION(2,12,0))
7300 compose->tooltips = tips;
7303 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7305 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7306 gtk_widget_set_size_request(window, prefs_common.compose_width,
7307 prefs_common.compose_height);
7309 if (!geometry.max_width) {
7310 geometry.max_width = gdk_screen_width();
7311 geometry.max_height = gdk_screen_height();
7314 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7315 &geometry, GDK_HINT_MAX_SIZE);
7316 if (!geometry.min_width) {
7317 geometry.min_width = 600;
7318 geometry.min_height = 440;
7320 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7321 &geometry, GDK_HINT_MIN_SIZE);
7323 #ifndef GENERIC_UMPC
7324 if (compose_force_window_origin)
7325 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7326 prefs_common.compose_y);
7328 g_signal_connect(G_OBJECT(window), "delete_event",
7329 G_CALLBACK(compose_delete_cb), compose);
7330 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7331 gtk_widget_realize(window);
7333 gtkut_widget_set_composer_icon(window);
7335 vbox = gtk_vbox_new(FALSE, 0);
7336 gtk_container_add(GTK_CONTAINER(window), vbox);
7338 compose->ui_manager = gtk_ui_manager_new();
7339 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7340 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7341 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7342 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7343 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7344 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7345 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7346 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7347 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7348 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7351 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7353 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7356 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7357 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7359 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7361 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7362 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7363 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7366 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7367 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7368 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7369 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7370 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7371 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7372 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7373 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7374 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7375 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7376 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7377 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7380 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7382 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7384 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7385 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7386 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7389 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7391 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7393 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7396 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7400 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7405 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7406 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7407 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7409 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7411 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7414 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7416 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7419 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7421 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7437 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7470 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)
7471 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)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7477 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)
7478 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)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7483 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)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7487 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)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7493 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)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7499 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)
7500 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)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7513 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)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7526 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7531 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7532 gtk_widget_show_all(menubar);
7534 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7536 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7538 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7541 if (prefs_common.toolbar_detachable) {
7542 handlebox = gtk_handle_box_new();
7544 handlebox = gtk_hbox_new(FALSE, 0);
7546 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7548 gtk_widget_realize(handlebox);
7550 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7553 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7557 vbox2 = gtk_vbox_new(FALSE, 2);
7558 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7559 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7562 notebook = gtk_notebook_new();
7563 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7564 gtk_widget_show(notebook);
7566 /* header labels and entries */
7567 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7568 compose_create_header(compose),
7569 gtk_label_new_with_mnemonic(_("Hea_der")));
7570 /* attachment list */
7571 attach_hbox = gtk_hbox_new(FALSE, 0);
7572 gtk_widget_show(attach_hbox);
7574 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7575 gtk_widget_show(attach_lab1);
7576 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7578 attach_lab2 = gtk_label_new("");
7579 gtk_widget_show(attach_lab2);
7580 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7582 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7583 compose_create_attach(compose),
7586 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7587 compose_create_others(compose),
7588 gtk_label_new_with_mnemonic(_("Othe_rs")));
7591 subject_hbox = gtk_hbox_new(FALSE, 0);
7592 gtk_widget_show(subject_hbox);
7594 subject_frame = gtk_frame_new(NULL);
7595 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7596 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7597 gtk_widget_show(subject_frame);
7599 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7600 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7601 gtk_widget_show(subject);
7603 label = gtk_label_new(_("Subject:"));
7604 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7605 gtk_widget_show(label);
7608 subject_entry = claws_spell_entry_new();
7610 subject_entry = gtk_entry_new();
7612 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7613 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7614 G_CALLBACK(compose_grab_focus_cb), compose);
7615 gtk_widget_show(subject_entry);
7616 compose->subject_entry = subject_entry;
7617 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7619 edit_vbox = gtk_vbox_new(FALSE, 0);
7621 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7624 ruler_hbox = gtk_hbox_new(FALSE, 0);
7625 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7627 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7628 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7629 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7633 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7634 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7635 GTK_POLICY_AUTOMATIC,
7636 GTK_POLICY_AUTOMATIC);
7637 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7639 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7641 text = gtk_text_view_new();
7642 if (prefs_common.show_compose_margin) {
7643 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7644 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7646 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7647 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7648 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7649 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7650 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7652 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7653 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7654 G_CALLBACK(compose_notebook_size_alloc), compose);
7655 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7656 G_CALLBACK(compose_edit_size_alloc),
7658 g_signal_connect(G_OBJECT(buffer), "changed",
7659 G_CALLBACK(compose_changed_cb), compose);
7660 g_signal_connect(G_OBJECT(text), "grab_focus",
7661 G_CALLBACK(compose_grab_focus_cb), compose);
7662 g_signal_connect(G_OBJECT(buffer), "insert_text",
7663 G_CALLBACK(text_inserted), compose);
7664 g_signal_connect(G_OBJECT(text), "button_press_event",
7665 G_CALLBACK(text_clicked), compose);
7667 g_signal_connect(G_OBJECT(text), "popup-menu",
7668 G_CALLBACK(compose_popup_menu), compose);
7670 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7671 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7672 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7673 G_CALLBACK(compose_popup_menu), compose);
7675 g_signal_connect(G_OBJECT(subject_entry), "changed",
7676 G_CALLBACK(compose_changed_cb), compose);
7677 g_signal_connect(G_OBJECT(subject_entry), "activate",
7678 G_CALLBACK(compose_subject_entry_activated), compose);
7681 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7682 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7683 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7684 g_signal_connect(G_OBJECT(text), "drag_data_received",
7685 G_CALLBACK(compose_insert_drag_received_cb),
7687 g_signal_connect(G_OBJECT(text), "drag-drop",
7688 G_CALLBACK(compose_drag_drop),
7690 g_signal_connect(G_OBJECT(text), "key-press-event",
7691 G_CALLBACK(completion_set_focus_to_subject),
7693 gtk_widget_show_all(vbox);
7695 /* pane between attach clist and text */
7696 paned = gtk_vpaned_new();
7697 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7699 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7700 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7702 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7704 gtk_paned_add1(GTK_PANED(paned), notebook);
7705 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7706 gtk_widget_show_all(paned);
7709 if (prefs_common.textfont) {
7710 PangoFontDescription *font_desc;
7712 font_desc = pango_font_description_from_string
7713 (prefs_common.textfont);
7715 gtk_widget_modify_font(text, font_desc);
7716 pango_font_description_free(font_desc);
7720 gtk_action_group_add_actions(action_group, compose_popup_entries,
7721 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7725 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7726 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7727 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7729 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7731 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7732 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7733 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7735 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7737 undostruct = undo_init(text);
7738 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7741 address_completion_start(window);
7743 compose->window = window;
7744 compose->vbox = vbox;
7745 compose->menubar = menubar;
7746 compose->handlebox = handlebox;
7748 compose->vbox2 = vbox2;
7750 compose->paned = paned;
7752 compose->attach_label = attach_lab2;
7754 compose->notebook = notebook;
7755 compose->edit_vbox = edit_vbox;
7756 compose->ruler_hbox = ruler_hbox;
7757 compose->ruler = ruler;
7758 compose->scrolledwin = scrolledwin;
7759 compose->text = text;
7761 compose->focused_editable = NULL;
7763 compose->popupmenu = popupmenu;
7765 compose->tmpl_menu = tmpl_menu;
7767 compose->mode = mode;
7768 compose->rmode = mode;
7770 compose->targetinfo = NULL;
7771 compose->replyinfo = NULL;
7772 compose->fwdinfo = NULL;
7774 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7775 g_str_equal, (GDestroyNotify) g_free, NULL);
7777 compose->replyto = NULL;
7779 compose->bcc = NULL;
7780 compose->followup_to = NULL;
7782 compose->ml_post = NULL;
7784 compose->inreplyto = NULL;
7785 compose->references = NULL;
7786 compose->msgid = NULL;
7787 compose->boundary = NULL;
7789 compose->autowrap = prefs_common.autowrap;
7790 compose->autoindent = prefs_common.auto_indent;
7791 compose->use_signing = FALSE;
7792 compose->use_encryption = FALSE;
7793 compose->privacy_system = NULL;
7795 compose->modified = FALSE;
7797 compose->return_receipt = FALSE;
7799 compose->to_list = NULL;
7800 compose->newsgroup_list = NULL;
7802 compose->undostruct = undostruct;
7804 compose->sig_str = NULL;
7806 compose->exteditor_file = NULL;
7807 compose->exteditor_pid = -1;
7808 compose->exteditor_tag = -1;
7809 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7812 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7813 if (mode != COMPOSE_REDIRECT) {
7814 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7815 strcmp(prefs_common.dictionary, "")) {
7816 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7817 prefs_common.alt_dictionary,
7818 conv_get_locale_charset_str(),
7819 prefs_common.misspelled_col,
7820 prefs_common.check_while_typing,
7821 prefs_common.recheck_when_changing_dict,
7822 prefs_common.use_alternate,
7823 prefs_common.use_both_dicts,
7824 GTK_TEXT_VIEW(text),
7825 GTK_WINDOW(compose->window),
7826 compose_dict_changed,
7827 compose_spell_menu_changed,
7830 alertpanel_error(_("Spell checker could not "
7832 gtkaspell_checkers_strerror());
7833 gtkaspell_checkers_reset_error();
7835 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7839 compose->gtkaspell = gtkaspell;
7840 compose_spell_menu_changed(compose);
7841 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7844 compose_select_account(compose, account, TRUE);
7846 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7847 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7849 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7850 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7852 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7853 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7855 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7856 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7858 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7859 if (account->protocol != A_NNTP)
7860 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7861 prefs_common_translated_header_name("To:"));
7863 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7864 prefs_common_translated_header_name("Newsgroups:"));
7866 #ifndef USE_NEW_ADDRBOOK
7867 addressbook_set_target_compose(compose);
7869 if (mode != COMPOSE_REDIRECT)
7870 compose_set_template_menu(compose);
7872 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7875 compose_list = g_list_append(compose_list, compose);
7877 if (!prefs_common.show_ruler)
7878 gtk_widget_hide(ruler_hbox);
7880 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7883 compose->priority = PRIORITY_NORMAL;
7884 compose_update_priority_menu_item(compose);
7886 compose_set_out_encoding(compose);
7889 compose_update_actions_menu(compose);
7891 /* Privacy Systems menu */
7892 compose_update_privacy_systems_menu(compose);
7894 activate_privacy_system(compose, account, TRUE);
7895 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7897 gtk_widget_realize(window);
7899 gtk_widget_show(window);
7901 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7902 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7909 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7914 GtkWidget *optmenubox;
7917 GtkWidget *from_name = NULL;
7918 #if !(GTK_CHECK_VERSION(2,12,0))
7919 GtkTooltips *tips = compose->tooltips;
7922 gint num = 0, def_menu = 0;
7924 accounts = account_get_list();
7925 cm_return_val_if_fail(accounts != NULL, NULL);
7927 optmenubox = gtk_event_box_new();
7928 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7929 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7931 hbox = gtk_hbox_new(FALSE, 6);
7932 from_name = gtk_entry_new();
7934 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7935 G_CALLBACK(compose_grab_focus_cb), compose);
7937 for (; accounts != NULL; accounts = accounts->next, num++) {
7938 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7939 gchar *name, *from = NULL;
7941 if (ac == compose->account) def_menu = num;
7943 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7946 if (ac == compose->account) {
7947 if (ac->name && *ac->name) {
7949 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7950 from = g_strdup_printf("%s <%s>",
7952 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7954 from = g_strdup_printf("%s",
7956 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7959 COMBOBOX_ADD(menu, name, ac->account_id);
7964 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7966 g_signal_connect(G_OBJECT(optmenu), "changed",
7967 G_CALLBACK(account_activated),
7969 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7970 G_CALLBACK(compose_entry_popup_extend),
7973 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7974 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7976 CLAWS_SET_TIP(optmenubox,
7977 _("Account to use for this email"));
7978 CLAWS_SET_TIP(from_name,
7979 _("Sender address to be used"));
7981 compose->account_combo = optmenu;
7982 compose->from_name = from_name;
7987 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7989 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7990 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7991 Compose *compose = (Compose *) data;
7993 compose->priority = value;
7997 static void compose_reply_change_mode(Compose *compose,
8000 gboolean was_modified = compose->modified;
8002 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8004 cm_return_if_fail(compose->replyinfo != NULL);
8006 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8008 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8010 if (action == COMPOSE_REPLY_TO_ALL)
8012 if (action == COMPOSE_REPLY_TO_SENDER)
8014 if (action == COMPOSE_REPLY_TO_LIST)
8017 compose_remove_header_entries(compose);
8018 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8019 if (compose->account->set_autocc && compose->account->auto_cc)
8020 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8022 if (compose->account->set_autobcc && compose->account->auto_bcc)
8023 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8025 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8026 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8027 compose_show_first_last_header(compose, TRUE);
8028 compose->modified = was_modified;
8029 compose_set_title(compose);
8032 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8034 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8035 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8036 Compose *compose = (Compose *) data;
8039 compose_reply_change_mode(compose, value);
8042 static void compose_update_priority_menu_item(Compose * compose)
8044 GtkWidget *menuitem = NULL;
8045 switch (compose->priority) {
8046 case PRIORITY_HIGHEST:
8047 menuitem = gtk_ui_manager_get_widget
8048 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8051 menuitem = gtk_ui_manager_get_widget
8052 (compose->ui_manager, "/Menu/Options/Priority/High");
8054 case PRIORITY_NORMAL:
8055 menuitem = gtk_ui_manager_get_widget
8056 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8059 menuitem = gtk_ui_manager_get_widget
8060 (compose->ui_manager, "/Menu/Options/Priority/Low");
8062 case PRIORITY_LOWEST:
8063 menuitem = gtk_ui_manager_get_widget
8064 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8067 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8070 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8072 Compose *compose = (Compose *) data;
8074 gboolean can_sign = FALSE, can_encrypt = FALSE;
8076 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8078 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8081 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8082 g_free(compose->privacy_system);
8083 compose->privacy_system = NULL;
8084 if (systemid != NULL) {
8085 compose->privacy_system = g_strdup(systemid);
8087 can_sign = privacy_system_can_sign(systemid);
8088 can_encrypt = privacy_system_can_encrypt(systemid);
8091 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8093 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8094 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8097 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8099 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8100 GtkWidget *menuitem = NULL;
8101 GList *children, *amenu;
8102 gboolean can_sign = FALSE, can_encrypt = FALSE;
8103 gboolean found = FALSE;
8105 if (compose->privacy_system != NULL) {
8107 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8108 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8109 cm_return_if_fail(menuitem != NULL);
8111 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8114 while (amenu != NULL) {
8115 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8116 if (systemid != NULL) {
8117 if (strcmp(systemid, compose->privacy_system) == 0 &&
8118 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8119 menuitem = GTK_WIDGET(amenu->data);
8121 can_sign = privacy_system_can_sign(systemid);
8122 can_encrypt = privacy_system_can_encrypt(systemid);
8126 } else if (strlen(compose->privacy_system) == 0 &&
8127 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8128 menuitem = GTK_WIDGET(amenu->data);
8131 can_encrypt = FALSE;
8136 amenu = amenu->next;
8138 g_list_free(children);
8139 if (menuitem != NULL)
8140 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8142 if (warn && !found && strlen(compose->privacy_system)) {
8143 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8144 "will not be able to sign or encrypt this message."),
8145 compose->privacy_system);
8149 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8150 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8153 static void compose_set_out_encoding(Compose *compose)
8155 CharSet out_encoding;
8156 const gchar *branch = NULL;
8157 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8159 switch(out_encoding) {
8160 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8161 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8162 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8163 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8164 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8165 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8166 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8167 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8168 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8169 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8170 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8171 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8172 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8173 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8174 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8175 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8176 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8177 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8178 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8179 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8180 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8181 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8182 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8183 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8184 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8185 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8186 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8187 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8188 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8189 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8190 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8191 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8192 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8194 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8197 static void compose_set_template_menu(Compose *compose)
8199 GSList *tmpl_list, *cur;
8203 tmpl_list = template_get_config();
8205 menu = gtk_menu_new();
8207 gtk_menu_set_accel_group (GTK_MENU (menu),
8208 gtk_ui_manager_get_accel_group(compose->ui_manager));
8209 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8210 Template *tmpl = (Template *)cur->data;
8211 gchar *accel_path = NULL;
8212 item = gtk_menu_item_new_with_label(tmpl->name);
8213 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8214 g_signal_connect(G_OBJECT(item), "activate",
8215 G_CALLBACK(compose_template_activate_cb),
8217 g_object_set_data(G_OBJECT(item), "template", tmpl);
8218 gtk_widget_show(item);
8219 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8220 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8224 gtk_widget_show(menu);
8225 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8228 void compose_update_actions_menu(Compose *compose)
8230 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8233 static void compose_update_privacy_systems_menu(Compose *compose)
8235 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8236 GSList *systems, *cur;
8238 GtkWidget *system_none;
8240 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8241 GtkWidget *privacy_menu = gtk_menu_new();
8243 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8244 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8246 g_signal_connect(G_OBJECT(system_none), "activate",
8247 G_CALLBACK(compose_set_privacy_system_cb), compose);
8249 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8250 gtk_widget_show(system_none);
8252 systems = privacy_get_system_ids();
8253 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8254 gchar *systemid = cur->data;
8256 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8257 widget = gtk_radio_menu_item_new_with_label(group,
8258 privacy_system_get_name(systemid));
8259 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8260 g_strdup(systemid), g_free);
8261 g_signal_connect(G_OBJECT(widget), "activate",
8262 G_CALLBACK(compose_set_privacy_system_cb), compose);
8264 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8265 gtk_widget_show(widget);
8268 g_slist_free(systems);
8269 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8270 gtk_widget_show_all(privacy_menu);
8271 gtk_widget_show_all(privacy_menuitem);
8274 void compose_reflect_prefs_all(void)
8279 for (cur = compose_list; cur != NULL; cur = cur->next) {
8280 compose = (Compose *)cur->data;
8281 compose_set_template_menu(compose);
8285 void compose_reflect_prefs_pixmap_theme(void)
8290 for (cur = compose_list; cur != NULL; cur = cur->next) {
8291 compose = (Compose *)cur->data;
8292 toolbar_update(TOOLBAR_COMPOSE, compose);
8296 static const gchar *compose_quote_char_from_context(Compose *compose)
8298 const gchar *qmark = NULL;
8300 cm_return_val_if_fail(compose != NULL, NULL);
8302 switch (compose->mode) {
8303 /* use forward-specific quote char */
8304 case COMPOSE_FORWARD:
8305 case COMPOSE_FORWARD_AS_ATTACH:
8306 case COMPOSE_FORWARD_INLINE:
8307 if (compose->folder && compose->folder->prefs &&
8308 compose->folder->prefs->forward_with_format)
8309 qmark = compose->folder->prefs->forward_quotemark;
8310 else if (compose->account->forward_with_format)
8311 qmark = compose->account->forward_quotemark;
8313 qmark = prefs_common.fw_quotemark;
8316 /* use reply-specific quote char in all other modes */
8318 if (compose->folder && compose->folder->prefs &&
8319 compose->folder->prefs->reply_with_format)
8320 qmark = compose->folder->prefs->reply_quotemark;
8321 else if (compose->account->reply_with_format)
8322 qmark = compose->account->reply_quotemark;
8324 qmark = prefs_common.quotemark;
8328 if (qmark == NULL || *qmark == '\0')
8334 static void compose_template_apply(Compose *compose, Template *tmpl,
8338 GtkTextBuffer *buffer;
8342 gchar *parsed_str = NULL;
8343 gint cursor_pos = 0;
8344 const gchar *err_msg = _("The body of the template has an error at line %d.");
8347 /* process the body */
8349 text = GTK_TEXT_VIEW(compose->text);
8350 buffer = gtk_text_view_get_buffer(text);
8353 qmark = compose_quote_char_from_context(compose);
8355 if (compose->replyinfo != NULL) {
8358 gtk_text_buffer_set_text(buffer, "", -1);
8359 mark = gtk_text_buffer_get_insert(buffer);
8360 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8362 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8363 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8365 } else if (compose->fwdinfo != NULL) {
8368 gtk_text_buffer_set_text(buffer, "", -1);
8369 mark = gtk_text_buffer_get_insert(buffer);
8370 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8372 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8373 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8376 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8378 GtkTextIter start, end;
8381 gtk_text_buffer_get_start_iter(buffer, &start);
8382 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8383 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8385 /* clear the buffer now */
8387 gtk_text_buffer_set_text(buffer, "", -1);
8389 parsed_str = compose_quote_fmt(compose, dummyinfo,
8390 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8391 procmsg_msginfo_free( dummyinfo );
8397 gtk_text_buffer_set_text(buffer, "", -1);
8398 mark = gtk_text_buffer_get_insert(buffer);
8399 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8402 if (replace && parsed_str && compose->account->auto_sig)
8403 compose_insert_sig(compose, FALSE);
8405 if (replace && parsed_str) {
8406 gtk_text_buffer_get_start_iter(buffer, &iter);
8407 gtk_text_buffer_place_cursor(buffer, &iter);
8411 cursor_pos = quote_fmt_get_cursor_pos();
8412 compose->set_cursor_pos = cursor_pos;
8413 if (cursor_pos == -1)
8415 gtk_text_buffer_get_start_iter(buffer, &iter);
8416 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8417 gtk_text_buffer_place_cursor(buffer, &iter);
8420 /* process the other fields */
8422 compose_template_apply_fields(compose, tmpl);
8423 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8424 quote_fmt_reset_vartable();
8425 compose_changed_cb(NULL, compose);
8428 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8429 gtkaspell_highlight_all(compose->gtkaspell);
8433 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8435 MsgInfo* dummyinfo = NULL;
8436 MsgInfo *msginfo = NULL;
8439 if (compose->replyinfo != NULL)
8440 msginfo = compose->replyinfo;
8441 else if (compose->fwdinfo != NULL)
8442 msginfo = compose->fwdinfo;
8444 dummyinfo = compose_msginfo_new_from_compose(compose);
8445 msginfo = dummyinfo;
8448 if (tmpl->from && *tmpl->from != '\0') {
8450 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8451 compose->gtkaspell);
8453 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8455 quote_fmt_scan_string(tmpl->from);
8458 buf = quote_fmt_get_buffer();
8460 alertpanel_error(_("Template From format error."));
8462 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8466 if (tmpl->to && *tmpl->to != '\0') {
8468 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8469 compose->gtkaspell);
8471 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8473 quote_fmt_scan_string(tmpl->to);
8476 buf = quote_fmt_get_buffer();
8478 alertpanel_error(_("Template To format error."));
8480 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8484 if (tmpl->cc && *tmpl->cc != '\0') {
8486 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8487 compose->gtkaspell);
8489 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8491 quote_fmt_scan_string(tmpl->cc);
8494 buf = quote_fmt_get_buffer();
8496 alertpanel_error(_("Template Cc format error."));
8498 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8502 if (tmpl->bcc && *tmpl->bcc != '\0') {
8504 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8505 compose->gtkaspell);
8507 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8509 quote_fmt_scan_string(tmpl->bcc);
8512 buf = quote_fmt_get_buffer();
8514 alertpanel_error(_("Template Bcc format error."));
8516 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8520 /* process the subject */
8521 if (tmpl->subject && *tmpl->subject != '\0') {
8523 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8524 compose->gtkaspell);
8526 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8528 quote_fmt_scan_string(tmpl->subject);
8531 buf = quote_fmt_get_buffer();
8533 alertpanel_error(_("Template subject format error."));
8535 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8539 procmsg_msginfo_free( dummyinfo );
8542 static void compose_destroy(Compose *compose)
8544 GtkAllocation allocation;
8545 GtkTextBuffer *buffer;
8546 GtkClipboard *clipboard;
8548 compose_list = g_list_remove(compose_list, compose);
8550 if (compose->updating) {
8551 debug_print("danger, not destroying anything now\n");
8552 compose->deferred_destroy = TRUE;
8555 /* NOTE: address_completion_end() does nothing with the window
8556 * however this may change. */
8557 address_completion_end(compose->window);
8559 slist_free_strings_full(compose->to_list);
8560 slist_free_strings_full(compose->newsgroup_list);
8561 slist_free_strings_full(compose->header_list);
8563 slist_free_strings_full(extra_headers);
8564 extra_headers = NULL;
8566 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8568 g_hash_table_destroy(compose->email_hashtable);
8570 procmsg_msginfo_free(compose->targetinfo);
8571 procmsg_msginfo_free(compose->replyinfo);
8572 procmsg_msginfo_free(compose->fwdinfo);
8574 g_free(compose->replyto);
8575 g_free(compose->cc);
8576 g_free(compose->bcc);
8577 g_free(compose->newsgroups);
8578 g_free(compose->followup_to);
8580 g_free(compose->ml_post);
8582 g_free(compose->inreplyto);
8583 g_free(compose->references);
8584 g_free(compose->msgid);
8585 g_free(compose->boundary);
8587 g_free(compose->redirect_filename);
8588 if (compose->undostruct)
8589 undo_destroy(compose->undostruct);
8591 g_free(compose->sig_str);
8593 g_free(compose->exteditor_file);
8595 g_free(compose->orig_charset);
8597 g_free(compose->privacy_system);
8599 #ifndef USE_NEW_ADDRBOOK
8600 if (addressbook_get_target_compose() == compose)
8601 addressbook_set_target_compose(NULL);
8604 if (compose->gtkaspell) {
8605 gtkaspell_delete(compose->gtkaspell);
8606 compose->gtkaspell = NULL;
8610 if (!compose->batch) {
8611 gtk_widget_get_allocation(compose->window, &allocation);
8612 prefs_common.compose_width = allocation.width;
8613 prefs_common.compose_height = allocation.height;
8616 if (!gtk_widget_get_parent(compose->paned))
8617 gtk_widget_destroy(compose->paned);
8618 gtk_widget_destroy(compose->popupmenu);
8620 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8621 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8622 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8624 gtk_widget_destroy(compose->window);
8625 toolbar_destroy(compose->toolbar);
8626 g_free(compose->toolbar);
8627 cm_mutex_free(compose->mutex);
8631 static void compose_attach_info_free(AttachInfo *ainfo)
8633 g_free(ainfo->file);
8634 g_free(ainfo->content_type);
8635 g_free(ainfo->name);
8636 g_free(ainfo->charset);
8640 static void compose_attach_update_label(Compose *compose)
8645 GtkTreeModel *model;
8650 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8651 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8652 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8656 while(gtk_tree_model_iter_next(model, &iter))
8659 text = g_strdup_printf("(%d)", i);
8660 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8664 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8666 Compose *compose = (Compose *)data;
8667 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8668 GtkTreeSelection *selection;
8670 GtkTreeModel *model;
8672 selection = gtk_tree_view_get_selection(tree_view);
8673 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8678 for (cur = sel; cur != NULL; cur = cur->next) {
8679 GtkTreePath *path = cur->data;
8680 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8683 gtk_tree_path_free(path);
8686 for (cur = sel; cur != NULL; cur = cur->next) {
8687 GtkTreeRowReference *ref = cur->data;
8688 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8691 if (gtk_tree_model_get_iter(model, &iter, path))
8692 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8694 gtk_tree_path_free(path);
8695 gtk_tree_row_reference_free(ref);
8699 compose_attach_update_label(compose);
8702 static struct _AttachProperty
8705 GtkWidget *mimetype_entry;
8706 GtkWidget *encoding_optmenu;
8707 GtkWidget *path_entry;
8708 GtkWidget *filename_entry;
8710 GtkWidget *cancel_btn;
8713 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8715 gtk_tree_path_free((GtkTreePath *)ptr);
8718 static void compose_attach_property(GtkAction *action, gpointer data)
8720 Compose *compose = (Compose *)data;
8721 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8723 GtkComboBox *optmenu;
8724 GtkTreeSelection *selection;
8726 GtkTreeModel *model;
8729 static gboolean cancelled;
8731 /* only if one selected */
8732 selection = gtk_tree_view_get_selection(tree_view);
8733 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8736 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8740 path = (GtkTreePath *) sel->data;
8741 gtk_tree_model_get_iter(model, &iter, path);
8742 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8745 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8751 if (!attach_prop.window)
8752 compose_attach_property_create(&cancelled);
8753 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8754 gtk_widget_grab_focus(attach_prop.ok_btn);
8755 gtk_widget_show(attach_prop.window);
8756 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8757 GTK_WINDOW(compose->window));
8759 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8760 if (ainfo->encoding == ENC_UNKNOWN)
8761 combobox_select_by_data(optmenu, ENC_BASE64);
8763 combobox_select_by_data(optmenu, ainfo->encoding);
8765 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8766 ainfo->content_type ? ainfo->content_type : "");
8767 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8768 ainfo->file ? ainfo->file : "");
8769 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8770 ainfo->name ? ainfo->name : "");
8773 const gchar *entry_text;
8775 gchar *cnttype = NULL;
8782 gtk_widget_hide(attach_prop.window);
8783 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8788 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8789 if (*entry_text != '\0') {
8792 text = g_strstrip(g_strdup(entry_text));
8793 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8794 cnttype = g_strdup(text);
8797 alertpanel_error(_("Invalid MIME type."));
8803 ainfo->encoding = combobox_get_active_data(optmenu);
8805 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8806 if (*entry_text != '\0') {
8807 if (is_file_exist(entry_text) &&
8808 (size = get_file_size(entry_text)) > 0)
8809 file = g_strdup(entry_text);
8812 (_("File doesn't exist or is empty."));
8818 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8819 if (*entry_text != '\0') {
8820 g_free(ainfo->name);
8821 ainfo->name = g_strdup(entry_text);
8825 g_free(ainfo->content_type);
8826 ainfo->content_type = cnttype;
8829 g_free(ainfo->file);
8833 ainfo->size = (goffset)size;
8835 /* update tree store */
8836 text = to_human_readable(ainfo->size);
8837 gtk_tree_model_get_iter(model, &iter, path);
8838 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8839 COL_MIMETYPE, ainfo->content_type,
8841 COL_NAME, ainfo->name,
8842 COL_CHARSET, ainfo->charset,
8848 gtk_tree_path_free(path);
8851 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8853 label = gtk_label_new(str); \
8854 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8855 GTK_FILL, 0, 0, 0); \
8856 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8858 entry = gtk_entry_new(); \
8859 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8860 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8863 static void compose_attach_property_create(gboolean *cancelled)
8869 GtkWidget *mimetype_entry;
8872 GtkListStore *optmenu_menu;
8873 GtkWidget *path_entry;
8874 GtkWidget *filename_entry;
8877 GtkWidget *cancel_btn;
8878 GList *mime_type_list, *strlist;
8881 debug_print("Creating attach_property window...\n");
8883 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8884 gtk_widget_set_size_request(window, 480, -1);
8885 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8886 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8887 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8888 g_signal_connect(G_OBJECT(window), "delete_event",
8889 G_CALLBACK(attach_property_delete_event),
8891 g_signal_connect(G_OBJECT(window), "key_press_event",
8892 G_CALLBACK(attach_property_key_pressed),
8895 vbox = gtk_vbox_new(FALSE, 8);
8896 gtk_container_add(GTK_CONTAINER(window), vbox);
8898 table = gtk_table_new(4, 2, FALSE);
8899 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8900 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8901 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8903 label = gtk_label_new(_("MIME type"));
8904 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8906 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8907 #if !GTK_CHECK_VERSION(2, 24, 0)
8908 mimetype_entry = gtk_combo_box_entry_new_text();
8910 mimetype_entry = gtk_combo_box_text_new_with_entry();
8912 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8913 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8915 /* stuff with list */
8916 mime_type_list = procmime_get_mime_type_list();
8918 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8919 MimeType *type = (MimeType *) mime_type_list->data;
8922 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8924 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8927 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8928 (GCompareFunc)strcmp2);
8931 for (mime_type_list = strlist; mime_type_list != NULL;
8932 mime_type_list = mime_type_list->next) {
8933 #if !GTK_CHECK_VERSION(2, 24, 0)
8934 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8936 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8938 g_free(mime_type_list->data);
8940 g_list_free(strlist);
8941 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8942 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8944 label = gtk_label_new(_("Encoding"));
8945 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8947 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8949 hbox = gtk_hbox_new(FALSE, 0);
8950 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8951 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8953 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8954 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8956 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8957 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8958 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8959 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8960 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8962 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8964 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8965 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8967 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8968 &ok_btn, GTK_STOCK_OK,
8970 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8971 gtk_widget_grab_default(ok_btn);
8973 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8974 G_CALLBACK(attach_property_ok),
8976 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8977 G_CALLBACK(attach_property_cancel),
8980 gtk_widget_show_all(vbox);
8982 attach_prop.window = window;
8983 attach_prop.mimetype_entry = mimetype_entry;
8984 attach_prop.encoding_optmenu = optmenu;
8985 attach_prop.path_entry = path_entry;
8986 attach_prop.filename_entry = filename_entry;
8987 attach_prop.ok_btn = ok_btn;
8988 attach_prop.cancel_btn = cancel_btn;
8991 #undef SET_LABEL_AND_ENTRY
8993 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8999 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9005 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9006 gboolean *cancelled)
9014 static gboolean attach_property_key_pressed(GtkWidget *widget,
9016 gboolean *cancelled)
9018 if (event && event->keyval == GDK_KEY_Escape) {
9022 if (event && event->keyval == GDK_KEY_Return) {
9030 static void compose_exec_ext_editor(Compose *compose)
9037 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9038 G_DIR_SEPARATOR, compose);
9040 if (pipe(pipe_fds) < 0) {
9046 if ((pid = fork()) < 0) {
9053 /* close the write side of the pipe */
9056 compose->exteditor_file = g_strdup(tmp);
9057 compose->exteditor_pid = pid;
9059 compose_set_ext_editor_sensitive(compose, FALSE);
9062 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9064 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9066 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9070 } else { /* process-monitoring process */
9076 /* close the read side of the pipe */
9079 if (compose_write_body_to_file(compose, tmp) < 0) {
9080 fd_write_all(pipe_fds[1], "2\n", 2);
9084 pid_ed = compose_exec_ext_editor_real(tmp);
9086 fd_write_all(pipe_fds[1], "1\n", 2);
9090 /* wait until editor is terminated */
9091 waitpid(pid_ed, NULL, 0);
9093 fd_write_all(pipe_fds[1], "0\n", 2);
9100 #endif /* G_OS_UNIX */
9104 static gint compose_exec_ext_editor_real(const gchar *file)
9111 cm_return_val_if_fail(file != NULL, -1);
9113 if ((pid = fork()) < 0) {
9118 if (pid != 0) return pid;
9120 /* grandchild process */
9122 if (setpgid(0, getppid()))
9125 if (prefs_common_get_ext_editor_cmd() &&
9126 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9127 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9128 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9130 if (prefs_common_get_ext_editor_cmd())
9131 g_warning("External editor command-line is invalid: '%s'\n",
9132 prefs_common_get_ext_editor_cmd());
9133 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9136 cmdline = strsplit_with_quote(buf, " ", 1024);
9137 execvp(cmdline[0], cmdline);
9140 g_strfreev(cmdline);
9145 static gboolean compose_ext_editor_kill(Compose *compose)
9147 pid_t pgid = compose->exteditor_pid * -1;
9150 ret = kill(pgid, 0);
9152 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9156 msg = g_strdup_printf
9157 (_("The external editor is still working.\n"
9158 "Force terminating the process?\n"
9159 "process group id: %d"), -pgid);
9160 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9161 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9165 if (val == G_ALERTALTERNATE) {
9166 g_source_remove(compose->exteditor_tag);
9167 g_io_channel_shutdown(compose->exteditor_ch,
9169 g_io_channel_unref(compose->exteditor_ch);
9171 if (kill(pgid, SIGTERM) < 0) perror("kill");
9172 waitpid(compose->exteditor_pid, NULL, 0);
9174 g_warning("Terminated process group id: %d", -pgid);
9175 g_warning("Temporary file: %s",
9176 compose->exteditor_file);
9178 compose_set_ext_editor_sensitive(compose, TRUE);
9180 g_free(compose->exteditor_file);
9181 compose->exteditor_file = NULL;
9182 compose->exteditor_pid = -1;
9183 compose->exteditor_ch = NULL;
9184 compose->exteditor_tag = -1;
9192 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9196 Compose *compose = (Compose *)data;
9199 debug_print("Compose: input from monitoring process\n");
9201 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9203 g_io_channel_shutdown(source, FALSE, NULL);
9204 g_io_channel_unref(source);
9206 waitpid(compose->exteditor_pid, NULL, 0);
9208 if (buf[0] == '0') { /* success */
9209 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9210 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9212 gtk_text_buffer_set_text(buffer, "", -1);
9213 compose_insert_file(compose, compose->exteditor_file);
9214 compose_changed_cb(NULL, compose);
9215 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9217 if (claws_unlink(compose->exteditor_file) < 0)
9218 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9219 } else if (buf[0] == '1') { /* failed */
9220 g_warning("Couldn't exec external editor\n");
9221 if (claws_unlink(compose->exteditor_file) < 0)
9222 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9223 } else if (buf[0] == '2') {
9224 g_warning("Couldn't write to file\n");
9225 } else if (buf[0] == '3') {
9226 g_warning("Pipe read failed\n");
9229 compose_set_ext_editor_sensitive(compose, TRUE);
9231 g_free(compose->exteditor_file);
9232 compose->exteditor_file = NULL;
9233 compose->exteditor_pid = -1;
9234 compose->exteditor_ch = NULL;
9235 compose->exteditor_tag = -1;
9240 static void compose_set_ext_editor_sensitive(Compose *compose,
9243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9247 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9248 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9249 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9251 gtk_widget_set_sensitive(compose->text, sensitive);
9252 if (compose->toolbar->send_btn)
9253 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9254 if (compose->toolbar->sendl_btn)
9255 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9256 if (compose->toolbar->draft_btn)
9257 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9258 if (compose->toolbar->insert_btn)
9259 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9260 if (compose->toolbar->sig_btn)
9261 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9262 if (compose->toolbar->exteditor_btn)
9263 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9264 if (compose->toolbar->linewrap_current_btn)
9265 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9266 if (compose->toolbar->linewrap_all_btn)
9267 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9269 #endif /* G_OS_UNIX */
9272 * compose_undo_state_changed:
9274 * Change the sensivity of the menuentries undo and redo
9276 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9277 gint redo_state, gpointer data)
9279 Compose *compose = (Compose *)data;
9281 switch (undo_state) {
9282 case UNDO_STATE_TRUE:
9283 if (!undostruct->undo_state) {
9284 undostruct->undo_state = TRUE;
9285 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9288 case UNDO_STATE_FALSE:
9289 if (undostruct->undo_state) {
9290 undostruct->undo_state = FALSE;
9291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9294 case UNDO_STATE_UNCHANGED:
9296 case UNDO_STATE_REFRESH:
9297 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9300 g_warning("Undo state not recognized");
9304 switch (redo_state) {
9305 case UNDO_STATE_TRUE:
9306 if (!undostruct->redo_state) {
9307 undostruct->redo_state = TRUE;
9308 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9311 case UNDO_STATE_FALSE:
9312 if (undostruct->redo_state) {
9313 undostruct->redo_state = FALSE;
9314 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9317 case UNDO_STATE_UNCHANGED:
9319 case UNDO_STATE_REFRESH:
9320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9323 g_warning("Redo state not recognized");
9328 /* callback functions */
9330 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9331 GtkAllocation *allocation,
9334 prefs_common.compose_notebook_height = allocation->height;
9337 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9338 * includes "non-client" (windows-izm) in calculation, so this calculation
9339 * may not be accurate.
9341 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9342 GtkAllocation *allocation,
9343 GtkSHRuler *shruler)
9345 if (prefs_common.show_ruler) {
9346 gint char_width = 0, char_height = 0;
9347 gint line_width_in_chars;
9349 gtkut_get_font_size(GTK_WIDGET(widget),
9350 &char_width, &char_height);
9351 line_width_in_chars =
9352 (allocation->width - allocation->x) / char_width;
9354 /* got the maximum */
9355 gtk_shruler_set_range(GTK_SHRULER(shruler),
9356 0.0, line_width_in_chars, 0);
9365 ComposePrefType type;
9366 gboolean entry_marked;
9369 static void account_activated(GtkComboBox *optmenu, gpointer data)
9371 Compose *compose = (Compose *)data;
9374 gchar *folderidentifier;
9375 gint account_id = 0;
9378 GSList *list, *saved_list = NULL;
9379 HeaderEntryState *state;
9380 GtkRcStyle *style = NULL;
9381 #if !GTK_CHECK_VERSION(3, 0, 0)
9382 static GdkColor yellow;
9383 static gboolean color_set = FALSE;
9385 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9388 /* Get ID of active account in the combo box */
9389 menu = gtk_combo_box_get_model(optmenu);
9390 gtk_combo_box_get_active_iter(optmenu, &iter);
9391 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9393 ac = account_find_from_id(account_id);
9394 cm_return_if_fail(ac != NULL);
9396 if (ac != compose->account) {
9397 compose_select_account(compose, ac, FALSE);
9399 for (list = compose->header_list; list; list = list->next) {
9400 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9402 if (hentry->type == PREF_ACCOUNT || !list->next) {
9403 compose_destroy_headerentry(compose, hentry);
9407 state = g_malloc0(sizeof(HeaderEntryState));
9408 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9409 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9410 state->entry = gtk_editable_get_chars(
9411 GTK_EDITABLE(hentry->entry), 0, -1);
9412 state->type = hentry->type;
9414 #if !GTK_CHECK_VERSION(3, 0, 0)
9416 gdk_color_parse("#f5f6be", &yellow);
9417 color_set = gdk_colormap_alloc_color(
9418 gdk_colormap_get_system(),
9419 &yellow, FALSE, TRUE);
9423 style = gtk_widget_get_modifier_style(hentry->entry);
9424 state->entry_marked = gdk_color_equal(&yellow,
9425 &style->base[GTK_STATE_NORMAL]);
9427 saved_list = g_slist_append(saved_list, state);
9428 compose_destroy_headerentry(compose, hentry);
9431 compose->header_last = NULL;
9432 g_slist_free(compose->header_list);
9433 compose->header_list = NULL;
9434 compose->header_nextrow = 1;
9435 compose_create_header_entry(compose);
9437 if (ac->set_autocc && ac->auto_cc)
9438 compose_entry_append(compose, ac->auto_cc,
9439 COMPOSE_CC, PREF_ACCOUNT);
9441 if (ac->set_autobcc && ac->auto_bcc)
9442 compose_entry_append(compose, ac->auto_bcc,
9443 COMPOSE_BCC, PREF_ACCOUNT);
9445 if (ac->set_autoreplyto && ac->auto_replyto)
9446 compose_entry_append(compose, ac->auto_replyto,
9447 COMPOSE_REPLYTO, PREF_ACCOUNT);
9449 for (list = saved_list; list; list = list->next) {
9450 state = (HeaderEntryState *) list->data;
9452 compose_add_header_entry(compose, state->header,
9453 state->entry, state->type);
9454 if (state->entry_marked)
9455 compose_entry_mark_default_to(compose, state->entry);
9457 g_free(state->header);
9458 g_free(state->entry);
9461 g_slist_free(saved_list);
9463 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9464 (ac->protocol == A_NNTP) ?
9465 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9468 /* Set message save folder */
9469 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9470 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9472 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9473 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9475 compose_set_save_to(compose, NULL);
9476 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9477 folderidentifier = folder_item_get_identifier(account_get_special_folder
9478 (compose->account, F_OUTBOX));
9479 compose_set_save_to(compose, folderidentifier);
9480 g_free(folderidentifier);
9484 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9485 GtkTreeViewColumn *column, Compose *compose)
9487 compose_attach_property(NULL, compose);
9490 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9493 Compose *compose = (Compose *)data;
9494 GtkTreeSelection *attach_selection;
9495 gint attach_nr_selected;
9497 if (!event) return FALSE;
9499 if (event->button == 3) {
9500 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9501 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9503 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9504 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9506 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9507 NULL, NULL, event->button, event->time);
9514 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9517 Compose *compose = (Compose *)data;
9519 if (!event) return FALSE;
9521 switch (event->keyval) {
9522 case GDK_KEY_Delete:
9523 compose_attach_remove_selected(NULL, compose);
9529 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9531 toolbar_comp_set_sensitive(compose, allow);
9532 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9533 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9537 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9541 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9545 static void compose_send_cb(GtkAction *action, gpointer data)
9547 Compose *compose = (Compose *)data;
9549 if (prefs_common.work_offline &&
9550 !inc_offline_should_override(TRUE,
9551 _("Claws Mail needs network access in order "
9552 "to send this email.")))
9555 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9556 g_source_remove(compose->draft_timeout_tag);
9557 compose->draft_timeout_tag = -1;
9560 compose_send(compose);
9563 static void compose_send_later_cb(GtkAction *action, gpointer data)
9565 Compose *compose = (Compose *)data;
9569 compose_allow_user_actions(compose, FALSE);
9570 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9571 compose_allow_user_actions(compose, TRUE);
9575 compose_close(compose);
9576 } else if (val == -1) {
9577 alertpanel_error(_("Could not queue message."));
9578 } else if (val == -2) {
9579 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9580 } else if (val == -3) {
9581 if (privacy_peek_error())
9582 alertpanel_error(_("Could not queue message for sending:\n\n"
9583 "Signature failed: %s"), privacy_get_error());
9584 } else if (val == -4) {
9585 alertpanel_error(_("Could not queue message for sending:\n\n"
9586 "Charset conversion failed."));
9587 } else if (val == -5) {
9588 alertpanel_error(_("Could not queue message for sending:\n\n"
9589 "Couldn't get recipient encryption key."));
9590 } else if (val == -6) {
9593 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9596 #define DRAFTED_AT_EXIT "drafted_at_exit"
9597 static void compose_register_draft(MsgInfo *info)
9599 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9600 DRAFTED_AT_EXIT, NULL);
9601 FILE *fp = g_fopen(filepath, "ab");
9604 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9612 gboolean compose_draft (gpointer data, guint action)
9614 Compose *compose = (Compose *)data;
9619 MsgFlags flag = {0, 0};
9620 static gboolean lock = FALSE;
9621 MsgInfo *newmsginfo;
9623 gboolean target_locked = FALSE;
9624 gboolean err = FALSE;
9626 if (lock) return FALSE;
9628 if (compose->sending)
9631 draft = account_get_special_folder(compose->account, F_DRAFT);
9632 cm_return_val_if_fail(draft != NULL, FALSE);
9634 if (!g_mutex_trylock(compose->mutex)) {
9635 /* we don't want to lock the mutex once it's available,
9636 * because as the only other part of compose.c locking
9637 * it is compose_close - which means once unlocked,
9638 * the compose struct will be freed */
9639 debug_print("couldn't lock mutex, probably sending\n");
9645 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9646 G_DIR_SEPARATOR, compose);
9647 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9648 FILE_OP_ERROR(tmp, "fopen");
9652 /* chmod for security */
9653 if (change_file_mode_rw(fp, tmp) < 0) {
9654 FILE_OP_ERROR(tmp, "chmod");
9655 g_warning("can't change file mode\n");
9658 /* Save draft infos */
9659 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9660 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9662 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9663 gchar *savefolderid;
9665 savefolderid = compose_get_save_to(compose);
9666 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9667 g_free(savefolderid);
9669 if (compose->return_receipt) {
9670 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9672 if (compose->privacy_system) {
9673 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9674 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9675 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9678 /* Message-ID of message replying to */
9679 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9682 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9683 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9686 /* Message-ID of message forwarding to */
9687 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9690 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9691 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9695 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9696 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9698 sheaders = compose_get_manual_headers_info(compose);
9699 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9702 /* end of headers */
9703 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9710 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9714 if (fclose(fp) == EOF) {
9718 if (compose->targetinfo) {
9719 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9720 flag.perm_flags = target_locked?MSG_LOCKED:0;
9722 flag.tmp_flags = MSG_DRAFT;
9724 folder_item_scan(draft);
9725 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9726 MsgInfo *tmpinfo = NULL;
9727 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9728 if (compose->msgid) {
9729 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9732 msgnum = tmpinfo->msgnum;
9733 procmsg_msginfo_free(tmpinfo);
9734 debug_print("got draft msgnum %d from scanning\n", msgnum);
9736 debug_print("didn't get draft msgnum after scanning\n");
9739 debug_print("got draft msgnum %d from adding\n", msgnum);
9745 if (action != COMPOSE_AUTO_SAVE) {
9746 if (action != COMPOSE_DRAFT_FOR_EXIT)
9747 alertpanel_error(_("Could not save draft."));
9750 gtkut_window_popup(compose->window);
9751 val = alertpanel_full(_("Could not save draft"),
9752 _("Could not save draft.\n"
9753 "Do you want to cancel exit or discard this email?"),
9754 _("_Cancel exit"), _("_Discard email"), NULL,
9755 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9756 if (val == G_ALERTALTERNATE) {
9758 g_mutex_unlock(compose->mutex); /* must be done before closing */
9759 compose_close(compose);
9763 g_mutex_unlock(compose->mutex); /* must be done before closing */
9772 if (compose->mode == COMPOSE_REEDIT) {
9773 compose_remove_reedit_target(compose, TRUE);
9776 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9779 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9781 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9783 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9784 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9785 procmsg_msginfo_set_flags(newmsginfo, 0,
9786 MSG_HAS_ATTACHMENT);
9788 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9789 compose_register_draft(newmsginfo);
9791 procmsg_msginfo_free(newmsginfo);
9794 folder_item_scan(draft);
9796 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9798 g_mutex_unlock(compose->mutex); /* must be done before closing */
9799 compose_close(compose);
9805 path = folder_item_fetch_msg(draft, msgnum);
9807 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9810 if (g_stat(path, &s) < 0) {
9811 FILE_OP_ERROR(path, "stat");
9817 procmsg_msginfo_free(compose->targetinfo);
9818 compose->targetinfo = procmsg_msginfo_new();
9819 compose->targetinfo->msgnum = msgnum;
9820 compose->targetinfo->size = (goffset)s.st_size;
9821 compose->targetinfo->mtime = s.st_mtime;
9822 compose->targetinfo->folder = draft;
9824 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9825 compose->mode = COMPOSE_REEDIT;
9827 if (action == COMPOSE_AUTO_SAVE) {
9828 compose->autosaved_draft = compose->targetinfo;
9830 compose->modified = FALSE;
9831 compose_set_title(compose);
9835 g_mutex_unlock(compose->mutex);
9839 void compose_clear_exit_drafts(void)
9841 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9842 DRAFTED_AT_EXIT, NULL);
9843 if (is_file_exist(filepath))
9844 claws_unlink(filepath);
9849 void compose_reopen_exit_drafts(void)
9851 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9852 DRAFTED_AT_EXIT, NULL);
9853 FILE *fp = g_fopen(filepath, "rb");
9857 while (fgets(buf, sizeof(buf), fp)) {
9858 gchar **parts = g_strsplit(buf, "\t", 2);
9859 const gchar *folder = parts[0];
9860 int msgnum = parts[1] ? atoi(parts[1]):-1;
9862 if (folder && *folder && msgnum > -1) {
9863 FolderItem *item = folder_find_item_from_identifier(folder);
9864 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9866 compose_reedit(info, FALSE);
9873 compose_clear_exit_drafts();
9876 static void compose_save_cb(GtkAction *action, gpointer data)
9878 Compose *compose = (Compose *)data;
9879 compose_draft(compose, COMPOSE_KEEP_EDITING);
9880 compose->rmode = COMPOSE_REEDIT;
9883 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9885 if (compose && file_list) {
9888 for ( tmp = file_list; tmp; tmp = tmp->next) {
9889 gchar *file = (gchar *) tmp->data;
9890 gchar *utf8_filename = conv_filename_to_utf8(file);
9891 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9892 compose_changed_cb(NULL, compose);
9897 g_free(utf8_filename);
9902 static void compose_attach_cb(GtkAction *action, gpointer data)
9904 Compose *compose = (Compose *)data;
9907 if (compose->redirect_filename != NULL)
9910 /* Set focus_window properly, in case we were called via popup menu,
9911 * which unsets it (via focus_out_event callback on compose window). */
9912 manage_window_focus_in(compose->window, NULL, NULL);
9914 file_list = filesel_select_multiple_files_open(_("Select file"));
9917 compose_attach_from_list(compose, file_list, TRUE);
9918 g_list_free(file_list);
9922 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9924 Compose *compose = (Compose *)data;
9926 gint files_inserted = 0;
9928 file_list = filesel_select_multiple_files_open(_("Select file"));
9933 for ( tmp = file_list; tmp; tmp = tmp->next) {
9934 gchar *file = (gchar *) tmp->data;
9935 gchar *filedup = g_strdup(file);
9936 gchar *shortfile = g_path_get_basename(filedup);
9937 ComposeInsertResult res;
9938 /* insert the file if the file is short or if the user confirmed that
9939 he/she wants to insert the large file */
9940 res = compose_insert_file(compose, file);
9941 if (res == COMPOSE_INSERT_READ_ERROR) {
9942 alertpanel_error(_("File '%s' could not be read."), shortfile);
9943 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9944 alertpanel_error(_("File '%s' contained invalid characters\n"
9945 "for the current encoding, insertion may be incorrect."),
9947 } else if (res == COMPOSE_INSERT_SUCCESS)
9954 g_list_free(file_list);
9958 if (files_inserted > 0 && compose->gtkaspell &&
9959 compose->gtkaspell->check_while_typing)
9960 gtkaspell_highlight_all(compose->gtkaspell);
9964 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9966 Compose *compose = (Compose *)data;
9968 compose_insert_sig(compose, FALSE);
9971 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9975 Compose *compose = (Compose *)data;
9977 gtkut_widget_get_uposition(widget, &x, &y);
9978 if (!compose->batch) {
9979 prefs_common.compose_x = x;
9980 prefs_common.compose_y = y;
9982 if (compose->sending || compose->updating)
9984 compose_close_cb(NULL, compose);
9988 void compose_close_toolbar(Compose *compose)
9990 compose_close_cb(NULL, compose);
9993 static void compose_close_cb(GtkAction *action, gpointer data)
9995 Compose *compose = (Compose *)data;
9999 if (compose->exteditor_tag != -1) {
10000 if (!compose_ext_editor_kill(compose))
10005 if (compose->modified) {
10006 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10007 if (!g_mutex_trylock(compose->mutex)) {
10008 /* we don't want to lock the mutex once it's available,
10009 * because as the only other part of compose.c locking
10010 * it is compose_close - which means once unlocked,
10011 * the compose struct will be freed */
10012 debug_print("couldn't lock mutex, probably sending\n");
10016 val = alertpanel(_("Discard message"),
10017 _("This message has been modified. Discard it?"),
10018 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10020 val = alertpanel(_("Save changes"),
10021 _("This message has been modified. Save the latest changes?"),
10022 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10024 g_mutex_unlock(compose->mutex);
10026 case G_ALERTDEFAULT:
10027 if (prefs_common.autosave && !reedit)
10028 compose_remove_draft(compose);
10030 case G_ALERTALTERNATE:
10031 compose_draft(data, COMPOSE_QUIT_EDITING);
10038 compose_close(compose);
10041 static void compose_print_cb(GtkAction *action, gpointer data)
10043 Compose *compose = (Compose *) data;
10045 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10046 if (compose->targetinfo)
10047 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10050 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10052 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10053 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10054 Compose *compose = (Compose *) data;
10057 compose->out_encoding = (CharSet)value;
10060 static void compose_address_cb(GtkAction *action, gpointer data)
10062 Compose *compose = (Compose *)data;
10064 #ifndef USE_NEW_ADDRBOOK
10065 addressbook_open(compose);
10067 GError* error = NULL;
10068 addressbook_connect_signals(compose);
10069 addressbook_dbus_open(TRUE, &error);
10071 g_warning("%s", error->message);
10072 g_error_free(error);
10077 static void about_show_cb(GtkAction *action, gpointer data)
10082 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10084 Compose *compose = (Compose *)data;
10089 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10090 cm_return_if_fail(tmpl != NULL);
10092 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
10094 val = alertpanel(_("Apply template"), msg,
10095 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10098 if (val == G_ALERTDEFAULT)
10099 compose_template_apply(compose, tmpl, TRUE);
10100 else if (val == G_ALERTALTERNATE)
10101 compose_template_apply(compose, tmpl, FALSE);
10104 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10106 Compose *compose = (Compose *)data;
10108 compose_exec_ext_editor(compose);
10111 static void compose_undo_cb(GtkAction *action, gpointer data)
10113 Compose *compose = (Compose *)data;
10114 gboolean prev_autowrap = compose->autowrap;
10116 compose->autowrap = FALSE;
10117 undo_undo(compose->undostruct);
10118 compose->autowrap = prev_autowrap;
10121 static void compose_redo_cb(GtkAction *action, gpointer data)
10123 Compose *compose = (Compose *)data;
10124 gboolean prev_autowrap = compose->autowrap;
10126 compose->autowrap = FALSE;
10127 undo_redo(compose->undostruct);
10128 compose->autowrap = prev_autowrap;
10131 static void entry_cut_clipboard(GtkWidget *entry)
10133 if (GTK_IS_EDITABLE(entry))
10134 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10135 else if (GTK_IS_TEXT_VIEW(entry))
10136 gtk_text_buffer_cut_clipboard(
10137 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10138 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10142 static void entry_copy_clipboard(GtkWidget *entry)
10144 if (GTK_IS_EDITABLE(entry))
10145 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10146 else if (GTK_IS_TEXT_VIEW(entry))
10147 gtk_text_buffer_copy_clipboard(
10148 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10149 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10152 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10153 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10155 if (GTK_IS_TEXT_VIEW(entry)) {
10156 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10157 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10158 GtkTextIter start_iter, end_iter;
10160 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10162 if (contents == NULL)
10165 /* we shouldn't delete the selection when middle-click-pasting, or we
10166 * can't mid-click-paste our own selection */
10167 if (clip != GDK_SELECTION_PRIMARY) {
10168 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10169 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10172 if (insert_place == NULL) {
10173 /* if insert_place isn't specified, insert at the cursor.
10174 * used for Ctrl-V pasting */
10175 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10176 start = gtk_text_iter_get_offset(&start_iter);
10177 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10179 /* if insert_place is specified, paste here.
10180 * used for mid-click-pasting */
10181 start = gtk_text_iter_get_offset(insert_place);
10182 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10183 if (prefs_common.primary_paste_unselects)
10184 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10188 /* paste unwrapped: mark the paste so it's not wrapped later */
10189 end = start + strlen(contents);
10190 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10191 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10192 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10193 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10194 /* rewrap paragraph now (after a mid-click-paste) */
10195 mark_start = gtk_text_buffer_get_insert(buffer);
10196 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10197 gtk_text_iter_backward_char(&start_iter);
10198 compose_beautify_paragraph(compose, &start_iter, TRUE);
10200 } else if (GTK_IS_EDITABLE(entry))
10201 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10203 compose->modified = TRUE;
10206 static void entry_allsel(GtkWidget *entry)
10208 if (GTK_IS_EDITABLE(entry))
10209 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10210 else if (GTK_IS_TEXT_VIEW(entry)) {
10211 GtkTextIter startiter, enditer;
10212 GtkTextBuffer *textbuf;
10214 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10215 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10216 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10218 gtk_text_buffer_move_mark_by_name(textbuf,
10219 "selection_bound", &startiter);
10220 gtk_text_buffer_move_mark_by_name(textbuf,
10221 "insert", &enditer);
10225 static void compose_cut_cb(GtkAction *action, gpointer data)
10227 Compose *compose = (Compose *)data;
10228 if (compose->focused_editable
10229 #ifndef GENERIC_UMPC
10230 && gtk_widget_has_focus(compose->focused_editable)
10233 entry_cut_clipboard(compose->focused_editable);
10236 static void compose_copy_cb(GtkAction *action, gpointer data)
10238 Compose *compose = (Compose *)data;
10239 if (compose->focused_editable
10240 #ifndef GENERIC_UMPC
10241 && gtk_widget_has_focus(compose->focused_editable)
10244 entry_copy_clipboard(compose->focused_editable);
10247 static void compose_paste_cb(GtkAction *action, gpointer data)
10249 Compose *compose = (Compose *)data;
10250 gint prev_autowrap;
10251 GtkTextBuffer *buffer;
10253 if (compose->focused_editable &&
10254 #ifndef GENERIC_UMPC
10255 gtk_widget_has_focus(compose->focused_editable)
10258 entry_paste_clipboard(compose, compose->focused_editable,
10259 prefs_common.linewrap_pastes,
10260 GDK_SELECTION_CLIPBOARD, NULL);
10265 #ifndef GENERIC_UMPC
10266 gtk_widget_has_focus(compose->text) &&
10268 compose->gtkaspell &&
10269 compose->gtkaspell->check_while_typing)
10270 gtkaspell_highlight_all(compose->gtkaspell);
10274 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10276 Compose *compose = (Compose *)data;
10277 gint wrap_quote = prefs_common.linewrap_quote;
10278 if (compose->focused_editable
10279 #ifndef GENERIC_UMPC
10280 && gtk_widget_has_focus(compose->focused_editable)
10283 /* let text_insert() (called directly or at a later time
10284 * after the gtk_editable_paste_clipboard) know that
10285 * text is to be inserted as a quotation. implemented
10286 * by using a simple refcount... */
10287 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10288 G_OBJECT(compose->focused_editable),
10289 "paste_as_quotation"));
10290 g_object_set_data(G_OBJECT(compose->focused_editable),
10291 "paste_as_quotation",
10292 GINT_TO_POINTER(paste_as_quotation + 1));
10293 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10294 entry_paste_clipboard(compose, compose->focused_editable,
10295 prefs_common.linewrap_pastes,
10296 GDK_SELECTION_CLIPBOARD, NULL);
10297 prefs_common.linewrap_quote = wrap_quote;
10301 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10303 Compose *compose = (Compose *)data;
10304 gint prev_autowrap;
10305 GtkTextBuffer *buffer;
10307 if (compose->focused_editable
10308 #ifndef GENERIC_UMPC
10309 && gtk_widget_has_focus(compose->focused_editable)
10312 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10313 GDK_SELECTION_CLIPBOARD, NULL);
10318 #ifndef GENERIC_UMPC
10319 gtk_widget_has_focus(compose->text) &&
10321 compose->gtkaspell &&
10322 compose->gtkaspell->check_while_typing)
10323 gtkaspell_highlight_all(compose->gtkaspell);
10327 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10329 Compose *compose = (Compose *)data;
10330 gint prev_autowrap;
10331 GtkTextBuffer *buffer;
10333 if (compose->focused_editable
10334 #ifndef GENERIC_UMPC
10335 && gtk_widget_has_focus(compose->focused_editable)
10338 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10339 GDK_SELECTION_CLIPBOARD, NULL);
10344 #ifndef GENERIC_UMPC
10345 gtk_widget_has_focus(compose->text) &&
10347 compose->gtkaspell &&
10348 compose->gtkaspell->check_while_typing)
10349 gtkaspell_highlight_all(compose->gtkaspell);
10353 static void compose_allsel_cb(GtkAction *action, gpointer data)
10355 Compose *compose = (Compose *)data;
10356 if (compose->focused_editable
10357 #ifndef GENERIC_UMPC
10358 && gtk_widget_has_focus(compose->focused_editable)
10361 entry_allsel(compose->focused_editable);
10364 static void textview_move_beginning_of_line (GtkTextView *text)
10366 GtkTextBuffer *buffer;
10370 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10372 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10373 mark = gtk_text_buffer_get_insert(buffer);
10374 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10375 gtk_text_iter_set_line_offset(&ins, 0);
10376 gtk_text_buffer_place_cursor(buffer, &ins);
10379 static void textview_move_forward_character (GtkTextView *text)
10381 GtkTextBuffer *buffer;
10385 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10387 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10388 mark = gtk_text_buffer_get_insert(buffer);
10389 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10390 if (gtk_text_iter_forward_cursor_position(&ins))
10391 gtk_text_buffer_place_cursor(buffer, &ins);
10394 static void textview_move_backward_character (GtkTextView *text)
10396 GtkTextBuffer *buffer;
10400 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10402 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10403 mark = gtk_text_buffer_get_insert(buffer);
10404 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10405 if (gtk_text_iter_backward_cursor_position(&ins))
10406 gtk_text_buffer_place_cursor(buffer, &ins);
10409 static void textview_move_forward_word (GtkTextView *text)
10411 GtkTextBuffer *buffer;
10416 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10418 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10419 mark = gtk_text_buffer_get_insert(buffer);
10420 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10421 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10422 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10423 gtk_text_iter_backward_word_start(&ins);
10424 gtk_text_buffer_place_cursor(buffer, &ins);
10428 static void textview_move_backward_word (GtkTextView *text)
10430 GtkTextBuffer *buffer;
10434 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10436 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10437 mark = gtk_text_buffer_get_insert(buffer);
10438 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10439 if (gtk_text_iter_backward_word_starts(&ins, 1))
10440 gtk_text_buffer_place_cursor(buffer, &ins);
10443 static void textview_move_end_of_line (GtkTextView *text)
10445 GtkTextBuffer *buffer;
10449 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10451 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10452 mark = gtk_text_buffer_get_insert(buffer);
10453 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10454 if (gtk_text_iter_forward_to_line_end(&ins))
10455 gtk_text_buffer_place_cursor(buffer, &ins);
10458 static void textview_move_next_line (GtkTextView *text)
10460 GtkTextBuffer *buffer;
10465 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10467 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10468 mark = gtk_text_buffer_get_insert(buffer);
10469 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10470 offset = gtk_text_iter_get_line_offset(&ins);
10471 if (gtk_text_iter_forward_line(&ins)) {
10472 gtk_text_iter_set_line_offset(&ins, offset);
10473 gtk_text_buffer_place_cursor(buffer, &ins);
10477 static void textview_move_previous_line (GtkTextView *text)
10479 GtkTextBuffer *buffer;
10484 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10486 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10487 mark = gtk_text_buffer_get_insert(buffer);
10488 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10489 offset = gtk_text_iter_get_line_offset(&ins);
10490 if (gtk_text_iter_backward_line(&ins)) {
10491 gtk_text_iter_set_line_offset(&ins, offset);
10492 gtk_text_buffer_place_cursor(buffer, &ins);
10496 static void textview_delete_forward_character (GtkTextView *text)
10498 GtkTextBuffer *buffer;
10500 GtkTextIter ins, end_iter;
10502 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10504 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10505 mark = gtk_text_buffer_get_insert(buffer);
10506 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10508 if (gtk_text_iter_forward_char(&end_iter)) {
10509 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10513 static void textview_delete_backward_character (GtkTextView *text)
10515 GtkTextBuffer *buffer;
10517 GtkTextIter ins, end_iter;
10519 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10521 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10522 mark = gtk_text_buffer_get_insert(buffer);
10523 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10525 if (gtk_text_iter_backward_char(&end_iter)) {
10526 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10530 static void textview_delete_forward_word (GtkTextView *text)
10532 GtkTextBuffer *buffer;
10534 GtkTextIter ins, end_iter;
10536 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10538 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10539 mark = gtk_text_buffer_get_insert(buffer);
10540 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10542 if (gtk_text_iter_forward_word_end(&end_iter)) {
10543 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10547 static void textview_delete_backward_word (GtkTextView *text)
10549 GtkTextBuffer *buffer;
10551 GtkTextIter ins, end_iter;
10553 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10555 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10556 mark = gtk_text_buffer_get_insert(buffer);
10557 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10559 if (gtk_text_iter_backward_word_start(&end_iter)) {
10560 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10564 static void textview_delete_line (GtkTextView *text)
10566 GtkTextBuffer *buffer;
10568 GtkTextIter ins, start_iter, end_iter;
10570 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10572 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10573 mark = gtk_text_buffer_get_insert(buffer);
10574 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10577 gtk_text_iter_set_line_offset(&start_iter, 0);
10580 if (gtk_text_iter_ends_line(&end_iter)){
10581 if (!gtk_text_iter_forward_char(&end_iter))
10582 gtk_text_iter_backward_char(&start_iter);
10585 gtk_text_iter_forward_to_line_end(&end_iter);
10586 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10589 static void textview_delete_to_line_end (GtkTextView *text)
10591 GtkTextBuffer *buffer;
10593 GtkTextIter ins, end_iter;
10595 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10597 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10598 mark = gtk_text_buffer_get_insert(buffer);
10599 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10601 if (gtk_text_iter_ends_line(&end_iter))
10602 gtk_text_iter_forward_char(&end_iter);
10604 gtk_text_iter_forward_to_line_end(&end_iter);
10605 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10608 #define DO_ACTION(name, act) { \
10609 if(!strcmp(name, a_name)) { \
10613 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10615 const gchar *a_name = gtk_action_get_name(action);
10616 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10617 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10618 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10619 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10620 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10621 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10622 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10623 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10624 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10625 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10626 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10627 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10628 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10629 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10633 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10635 Compose *compose = (Compose *)data;
10636 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10637 ComposeCallAdvancedAction action = -1;
10639 action = compose_call_advanced_action_from_path(gaction);
10642 void (*do_action) (GtkTextView *text);
10643 } action_table[] = {
10644 {textview_move_beginning_of_line},
10645 {textview_move_forward_character},
10646 {textview_move_backward_character},
10647 {textview_move_forward_word},
10648 {textview_move_backward_word},
10649 {textview_move_end_of_line},
10650 {textview_move_next_line},
10651 {textview_move_previous_line},
10652 {textview_delete_forward_character},
10653 {textview_delete_backward_character},
10654 {textview_delete_forward_word},
10655 {textview_delete_backward_word},
10656 {textview_delete_line},
10657 {textview_delete_to_line_end}
10660 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10662 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10663 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10664 if (action_table[action].do_action)
10665 action_table[action].do_action(text);
10667 g_warning("Not implemented yet.");
10671 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10673 GtkAllocation allocation;
10677 if (GTK_IS_EDITABLE(widget)) {
10678 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10679 gtk_editable_set_position(GTK_EDITABLE(widget),
10682 if ((parent = gtk_widget_get_parent(widget))
10683 && (parent = gtk_widget_get_parent(parent))
10684 && (parent = gtk_widget_get_parent(parent))) {
10685 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10686 gtk_widget_get_allocation(widget, &allocation);
10687 gint y = allocation.y;
10688 gint height = allocation.height;
10689 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10690 (GTK_SCROLLED_WINDOW(parent));
10692 gfloat value = gtk_adjustment_get_value(shown);
10693 gfloat upper = gtk_adjustment_get_upper(shown);
10694 gfloat page_size = gtk_adjustment_get_page_size(shown);
10695 if (y < (int)value) {
10696 gtk_adjustment_set_value(shown, y - 1);
10698 if ((y + height) > ((int)value + (int)page_size)) {
10699 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10700 gtk_adjustment_set_value(shown,
10701 y + height - (int)page_size - 1);
10703 gtk_adjustment_set_value(shown,
10704 (int)upper - (int)page_size - 1);
10711 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10712 compose->focused_editable = widget;
10714 #ifdef GENERIC_UMPC
10715 if (GTK_IS_TEXT_VIEW(widget)
10716 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10717 g_object_ref(compose->notebook);
10718 g_object_ref(compose->edit_vbox);
10719 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10720 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10721 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10722 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10723 g_object_unref(compose->notebook);
10724 g_object_unref(compose->edit_vbox);
10725 g_signal_handlers_block_by_func(G_OBJECT(widget),
10726 G_CALLBACK(compose_grab_focus_cb),
10728 gtk_widget_grab_focus(widget);
10729 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10730 G_CALLBACK(compose_grab_focus_cb),
10732 } else if (!GTK_IS_TEXT_VIEW(widget)
10733 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10734 g_object_ref(compose->notebook);
10735 g_object_ref(compose->edit_vbox);
10736 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10737 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10738 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10739 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10740 g_object_unref(compose->notebook);
10741 g_object_unref(compose->edit_vbox);
10742 g_signal_handlers_block_by_func(G_OBJECT(widget),
10743 G_CALLBACK(compose_grab_focus_cb),
10745 gtk_widget_grab_focus(widget);
10746 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10747 G_CALLBACK(compose_grab_focus_cb),
10753 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10755 compose->modified = TRUE;
10756 // compose_beautify_paragraph(compose, NULL, TRUE);
10757 #ifndef GENERIC_UMPC
10758 compose_set_title(compose);
10762 static void compose_wrap_cb(GtkAction *action, gpointer data)
10764 Compose *compose = (Compose *)data;
10765 compose_beautify_paragraph(compose, NULL, TRUE);
10768 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10770 Compose *compose = (Compose *)data;
10771 compose_wrap_all_full(compose, TRUE);
10774 static void compose_find_cb(GtkAction *action, gpointer data)
10776 Compose *compose = (Compose *)data;
10778 message_search_compose(compose);
10781 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10784 Compose *compose = (Compose *)data;
10785 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10786 if (compose->autowrap)
10787 compose_wrap_all_full(compose, TRUE);
10788 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10791 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10794 Compose *compose = (Compose *)data;
10795 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10798 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10800 Compose *compose = (Compose *)data;
10802 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10805 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10807 Compose *compose = (Compose *)data;
10809 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10812 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10814 g_free(compose->privacy_system);
10816 compose->privacy_system = g_strdup(account->default_privacy_system);
10817 compose_update_privacy_system_menu_item(compose, warn);
10820 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10822 Compose *compose = (Compose *)data;
10824 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10825 gtk_widget_show(compose->ruler_hbox);
10826 prefs_common.show_ruler = TRUE;
10828 gtk_widget_hide(compose->ruler_hbox);
10829 gtk_widget_queue_resize(compose->edit_vbox);
10830 prefs_common.show_ruler = FALSE;
10834 static void compose_attach_drag_received_cb (GtkWidget *widget,
10835 GdkDragContext *context,
10838 GtkSelectionData *data,
10841 gpointer user_data)
10843 Compose *compose = (Compose *)user_data;
10847 type = gtk_selection_data_get_data_type(data);
10848 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10850 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10852 ) && gtk_drag_get_source_widget(context) !=
10853 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10854 list = uri_list_extract_filenames(
10855 (const gchar *)gtk_selection_data_get_data(data));
10856 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10857 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10858 compose_attach_append
10859 (compose, (const gchar *)tmp->data,
10860 utf8_filename, NULL, NULL);
10861 g_free(utf8_filename);
10863 if (list) compose_changed_cb(NULL, compose);
10864 list_free_strings(list);
10866 } else if (gtk_drag_get_source_widget(context)
10867 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10868 /* comes from our summaryview */
10869 SummaryView * summaryview = NULL;
10870 GSList * list = NULL, *cur = NULL;
10872 if (mainwindow_get_mainwindow())
10873 summaryview = mainwindow_get_mainwindow()->summaryview;
10876 list = summary_get_selected_msg_list(summaryview);
10878 for (cur = list; cur; cur = cur->next) {
10879 MsgInfo *msginfo = (MsgInfo *)cur->data;
10880 gchar *file = NULL;
10882 file = procmsg_get_message_file_full(msginfo,
10885 compose_attach_append(compose, (const gchar *)file,
10886 (const gchar *)file, "message/rfc822", NULL);
10890 g_slist_free(list);
10894 static gboolean compose_drag_drop(GtkWidget *widget,
10895 GdkDragContext *drag_context,
10897 guint time, gpointer user_data)
10899 /* not handling this signal makes compose_insert_drag_received_cb
10904 static gboolean completion_set_focus_to_subject
10905 (GtkWidget *widget,
10906 GdkEventKey *event,
10909 cm_return_val_if_fail(compose != NULL, FALSE);
10911 /* make backtab move to subject field */
10912 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10913 gtk_widget_grab_focus(compose->subject_entry);
10919 static void compose_insert_drag_received_cb (GtkWidget *widget,
10920 GdkDragContext *drag_context,
10923 GtkSelectionData *data,
10926 gpointer user_data)
10928 Compose *compose = (Compose *)user_data;
10932 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10934 type = gtk_selection_data_get_data_type(data);
10936 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10938 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10940 AlertValue val = G_ALERTDEFAULT;
10941 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10943 list = uri_list_extract_filenames(ddata);
10944 if (list == NULL && strstr(ddata, "://")) {
10945 /* Assume a list of no files, and data has ://, is a remote link */
10946 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10947 gchar *tmpfile = get_tmp_file();
10948 str_write_to_file(tmpdata, tmpfile);
10950 compose_insert_file(compose, tmpfile);
10951 claws_unlink(tmpfile);
10953 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10954 compose_beautify_paragraph(compose, NULL, TRUE);
10957 switch (prefs_common.compose_dnd_mode) {
10958 case COMPOSE_DND_ASK:
10959 val = alertpanel_full(_("Insert or attach?"),
10960 _("Do you want to insert the contents of the file(s) "
10961 "into the message body, or attach it to the email?"),
10962 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10963 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10965 case COMPOSE_DND_INSERT:
10966 val = G_ALERTALTERNATE;
10968 case COMPOSE_DND_ATTACH:
10969 val = G_ALERTOTHER;
10972 /* unexpected case */
10973 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10976 if (val & G_ALERTDISABLE) {
10977 val &= ~G_ALERTDISABLE;
10978 /* remember what action to perform by default, only if we don't click Cancel */
10979 if (val == G_ALERTALTERNATE)
10980 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10981 else if (val == G_ALERTOTHER)
10982 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10985 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10986 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10987 list_free_strings(list);
10990 } else if (val == G_ALERTOTHER) {
10991 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10992 list_free_strings(list);
10997 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10998 compose_insert_file(compose, (const gchar *)tmp->data);
11000 list_free_strings(list);
11002 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11007 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11010 static void compose_header_drag_received_cb (GtkWidget *widget,
11011 GdkDragContext *drag_context,
11014 GtkSelectionData *data,
11017 gpointer user_data)
11019 GtkEditable *entry = (GtkEditable *)user_data;
11020 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11022 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11025 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11026 gchar *decoded=g_new(gchar, strlen(email));
11029 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11030 gtk_editable_delete_text(entry, 0, -1);
11031 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11032 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11036 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11039 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11041 Compose *compose = (Compose *)data;
11043 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11044 compose->return_receipt = TRUE;
11046 compose->return_receipt = FALSE;
11049 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11051 Compose *compose = (Compose *)data;
11053 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11054 compose->remove_references = TRUE;
11056 compose->remove_references = FALSE;
11059 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11060 ComposeHeaderEntry *headerentry)
11062 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11066 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11067 GdkEventKey *event,
11068 ComposeHeaderEntry *headerentry)
11070 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11071 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11072 !(event->state & GDK_MODIFIER_MASK) &&
11073 (event->keyval == GDK_KEY_BackSpace) &&
11074 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11075 gtk_container_remove
11076 (GTK_CONTAINER(headerentry->compose->header_table),
11077 headerentry->combo);
11078 gtk_container_remove
11079 (GTK_CONTAINER(headerentry->compose->header_table),
11080 headerentry->entry);
11081 headerentry->compose->header_list =
11082 g_slist_remove(headerentry->compose->header_list,
11084 g_free(headerentry);
11085 } else if (event->keyval == GDK_KEY_Tab) {
11086 if (headerentry->compose->header_last == headerentry) {
11087 /* Override default next focus, and give it to subject_entry
11088 * instead of notebook tabs
11090 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11091 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11098 static gboolean scroll_postpone(gpointer data)
11100 Compose *compose = (Compose *)data;
11102 cm_return_val_if_fail(!compose->batch, FALSE);
11104 GTK_EVENTS_FLUSH();
11105 compose_show_first_last_header(compose, FALSE);
11109 static void compose_headerentry_changed_cb(GtkWidget *entry,
11110 ComposeHeaderEntry *headerentry)
11112 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11113 compose_create_header_entry(headerentry->compose);
11114 g_signal_handlers_disconnect_matched
11115 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11116 0, 0, NULL, NULL, headerentry);
11118 if (!headerentry->compose->batch)
11119 g_timeout_add(0, scroll_postpone, headerentry->compose);
11123 static gboolean compose_defer_auto_save_draft(Compose *compose)
11125 compose->draft_timeout_tag = -1;
11126 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11130 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11132 GtkAdjustment *vadj;
11134 cm_return_if_fail(compose);
11135 cm_return_if_fail(!compose->batch);
11136 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11137 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11138 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11139 gtk_widget_get_parent(compose->header_table)));
11140 gtk_adjustment_set_value(vadj, (show_first ?
11141 gtk_adjustment_get_lower(vadj) :
11142 (gtk_adjustment_get_upper(vadj) -
11143 gtk_adjustment_get_page_size(vadj))));
11144 gtk_adjustment_changed(vadj);
11147 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11148 const gchar *text, gint len, Compose *compose)
11150 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11151 (G_OBJECT(compose->text), "paste_as_quotation"));
11154 cm_return_if_fail(text != NULL);
11156 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11157 G_CALLBACK(text_inserted),
11159 if (paste_as_quotation) {
11161 const gchar *qmark;
11163 GtkTextIter start_iter;
11166 len = strlen(text);
11168 new_text = g_strndup(text, len);
11170 qmark = compose_quote_char_from_context(compose);
11172 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11173 gtk_text_buffer_place_cursor(buffer, iter);
11175 pos = gtk_text_iter_get_offset(iter);
11177 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11178 _("Quote format error at line %d."));
11179 quote_fmt_reset_vartable();
11181 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11182 GINT_TO_POINTER(paste_as_quotation - 1));
11184 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11185 gtk_text_buffer_place_cursor(buffer, iter);
11186 gtk_text_buffer_delete_mark(buffer, mark);
11188 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11189 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11190 compose_beautify_paragraph(compose, &start_iter, FALSE);
11191 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11192 gtk_text_buffer_delete_mark(buffer, mark);
11194 if (strcmp(text, "\n") || compose->automatic_break
11195 || gtk_text_iter_starts_line(iter)) {
11196 GtkTextIter before_ins;
11197 gtk_text_buffer_insert(buffer, iter, text, len);
11198 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11199 before_ins = *iter;
11200 gtk_text_iter_backward_chars(&before_ins, len);
11201 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11204 /* check if the preceding is just whitespace or quote */
11205 GtkTextIter start_line;
11206 gchar *tmp = NULL, *quote = NULL;
11207 gint quote_len = 0, is_normal = 0;
11208 start_line = *iter;
11209 gtk_text_iter_set_line_offset(&start_line, 0);
11210 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11213 if (*tmp == '\0') {
11216 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11224 gtk_text_buffer_insert(buffer, iter, text, len);
11226 gtk_text_buffer_insert_with_tags_by_name(buffer,
11227 iter, text, len, "no_join", NULL);
11232 if (!paste_as_quotation) {
11233 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11234 compose_beautify_paragraph(compose, iter, FALSE);
11235 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11236 gtk_text_buffer_delete_mark(buffer, mark);
11239 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11240 G_CALLBACK(text_inserted),
11242 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11244 if (prefs_common.autosave &&
11245 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11246 compose->draft_timeout_tag != -2 /* disabled while loading */)
11247 compose->draft_timeout_tag = g_timeout_add
11248 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11252 static void compose_check_all(GtkAction *action, gpointer data)
11254 Compose *compose = (Compose *)data;
11255 if (!compose->gtkaspell)
11258 if (gtk_widget_has_focus(compose->subject_entry))
11259 claws_spell_entry_check_all(
11260 CLAWS_SPELL_ENTRY(compose->subject_entry));
11262 gtkaspell_check_all(compose->gtkaspell);
11265 static void compose_highlight_all(GtkAction *action, gpointer data)
11267 Compose *compose = (Compose *)data;
11268 if (compose->gtkaspell) {
11269 claws_spell_entry_recheck_all(
11270 CLAWS_SPELL_ENTRY(compose->subject_entry));
11271 gtkaspell_highlight_all(compose->gtkaspell);
11275 static void compose_check_backwards(GtkAction *action, gpointer data)
11277 Compose *compose = (Compose *)data;
11278 if (!compose->gtkaspell) {
11279 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11283 if (gtk_widget_has_focus(compose->subject_entry))
11284 claws_spell_entry_check_backwards(
11285 CLAWS_SPELL_ENTRY(compose->subject_entry));
11287 gtkaspell_check_backwards(compose->gtkaspell);
11290 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11292 Compose *compose = (Compose *)data;
11293 if (!compose->gtkaspell) {
11294 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11298 if (gtk_widget_has_focus(compose->subject_entry))
11299 claws_spell_entry_check_forwards_go(
11300 CLAWS_SPELL_ENTRY(compose->subject_entry));
11302 gtkaspell_check_forwards_go(compose->gtkaspell);
11307 *\brief Guess originating forward account from MsgInfo and several
11308 * "common preference" settings. Return NULL if no guess.
11310 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11312 PrefsAccount *account = NULL;
11314 cm_return_val_if_fail(msginfo, NULL);
11315 cm_return_val_if_fail(msginfo->folder, NULL);
11316 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11318 if (msginfo->folder->prefs->enable_default_account)
11319 account = account_find_from_id(msginfo->folder->prefs->default_account);
11322 account = msginfo->folder->folder->account;
11324 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11326 Xstrdup_a(to, msginfo->to, return NULL);
11327 extract_address(to);
11328 account = account_find_from_address(to, FALSE);
11331 if (!account && prefs_common.forward_account_autosel) {
11332 gchar cc[BUFFSIZE];
11333 if (!procheader_get_header_from_msginfo
11334 (msginfo, cc,sizeof cc , "Cc:")) {
11335 gchar *buf = cc + strlen("Cc:");
11336 extract_address(buf);
11337 account = account_find_from_address(buf, FALSE);
11341 if (!account && prefs_common.forward_account_autosel) {
11342 gchar deliveredto[BUFFSIZE];
11343 if (!procheader_get_header_from_msginfo
11344 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11345 gchar *buf = deliveredto + strlen("Delivered-To:");
11346 extract_address(buf);
11347 account = account_find_from_address(buf, FALSE);
11354 gboolean compose_close(Compose *compose)
11358 if (!g_mutex_trylock(compose->mutex)) {
11359 /* we have to wait for the (possibly deferred by auto-save)
11360 * drafting to be done, before destroying the compose under
11362 debug_print("waiting for drafting to finish...\n");
11363 compose_allow_user_actions(compose, FALSE);
11364 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11367 cm_return_val_if_fail(compose, FALSE);
11368 gtkut_widget_get_uposition(compose->window, &x, &y);
11369 if (!compose->batch) {
11370 prefs_common.compose_x = x;
11371 prefs_common.compose_y = y;
11373 g_mutex_unlock(compose->mutex);
11374 compose_destroy(compose);
11379 * Add entry field for each address in list.
11380 * \param compose E-Mail composition object.
11381 * \param listAddress List of (formatted) E-Mail addresses.
11383 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11386 node = listAddress;
11388 addr = ( gchar * ) node->data;
11389 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11390 node = g_list_next( node );
11394 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11395 guint action, gboolean opening_multiple)
11397 gchar *body = NULL;
11398 GSList *new_msglist = NULL;
11399 MsgInfo *tmp_msginfo = NULL;
11400 gboolean originally_enc = FALSE;
11401 gboolean originally_sig = FALSE;
11402 Compose *compose = NULL;
11403 gchar *s_system = NULL;
11405 cm_return_if_fail(msgview != NULL);
11407 cm_return_if_fail(msginfo_list != NULL);
11409 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11410 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11411 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11413 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11414 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11415 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11416 orig_msginfo, mimeinfo);
11417 if (tmp_msginfo != NULL) {
11418 new_msglist = g_slist_append(NULL, tmp_msginfo);
11420 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11421 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11422 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11424 tmp_msginfo->folder = orig_msginfo->folder;
11425 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11426 if (orig_msginfo->tags) {
11427 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11428 tmp_msginfo->folder->tags_dirty = TRUE;
11434 if (!opening_multiple)
11435 body = messageview_get_selection(msgview);
11438 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11439 procmsg_msginfo_free(tmp_msginfo);
11440 g_slist_free(new_msglist);
11442 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11444 if (compose && originally_enc) {
11445 compose_force_encryption(compose, compose->account, FALSE, s_system);
11448 if (compose && originally_sig && compose->account->default_sign_reply) {
11449 compose_force_signing(compose, compose->account, s_system);
11453 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11456 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11459 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11460 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11461 GSList *cur = msginfo_list;
11462 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11463 "messages. Opening the windows "
11464 "could take some time. Do you "
11465 "want to continue?"),
11466 g_slist_length(msginfo_list));
11467 if (g_slist_length(msginfo_list) > 9
11468 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11469 != G_ALERTALTERNATE) {
11474 /* We'll open multiple compose windows */
11475 /* let the WM place the next windows */
11476 compose_force_window_origin = FALSE;
11477 for (; cur; cur = cur->next) {
11479 tmplist.data = cur->data;
11480 tmplist.next = NULL;
11481 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11483 compose_force_window_origin = TRUE;
11485 /* forwarding multiple mails as attachments is done via a
11486 * single compose window */
11487 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11491 void compose_check_for_email_account(Compose *compose)
11493 PrefsAccount *ac = NULL, *curr = NULL;
11499 if (compose->account && compose->account->protocol == A_NNTP) {
11500 ac = account_get_cur_account();
11501 if (ac->protocol == A_NNTP) {
11502 list = account_get_list();
11504 for( ; list != NULL ; list = g_list_next(list)) {
11505 curr = (PrefsAccount *) list->data;
11506 if (curr->protocol != A_NNTP) {
11512 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11517 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11518 const gchar *address)
11520 GSList *msginfo_list = NULL;
11521 gchar *body = messageview_get_selection(msgview);
11524 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11526 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11527 compose_check_for_email_account(compose);
11528 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11529 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11530 compose_reply_set_subject(compose, msginfo);
11533 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11536 void compose_set_position(Compose *compose, gint pos)
11538 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11540 gtkut_text_view_set_position(text, pos);
11543 gboolean compose_search_string(Compose *compose,
11544 const gchar *str, gboolean case_sens)
11546 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11548 return gtkut_text_view_search_string(text, str, case_sens);
11551 gboolean compose_search_string_backward(Compose *compose,
11552 const gchar *str, gboolean case_sens)
11554 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11556 return gtkut_text_view_search_string_backward(text, str, case_sens);
11559 /* allocate a msginfo structure and populate its data from a compose data structure */
11560 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11562 MsgInfo *newmsginfo;
11564 gchar buf[BUFFSIZE];
11566 cm_return_val_if_fail( compose != NULL, NULL );
11568 newmsginfo = procmsg_msginfo_new();
11571 get_rfc822_date(buf, sizeof(buf));
11572 newmsginfo->date = g_strdup(buf);
11575 if (compose->from_name) {
11576 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11577 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11581 if (compose->subject_entry)
11582 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11584 /* to, cc, reply-to, newsgroups */
11585 for (list = compose->header_list; list; list = list->next) {
11586 gchar *header = gtk_editable_get_chars(
11588 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11589 gchar *entry = gtk_editable_get_chars(
11590 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11592 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11593 if ( newmsginfo->to == NULL ) {
11594 newmsginfo->to = g_strdup(entry);
11595 } else if (entry && *entry) {
11596 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11597 g_free(newmsginfo->to);
11598 newmsginfo->to = tmp;
11601 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11602 if ( newmsginfo->cc == NULL ) {
11603 newmsginfo->cc = g_strdup(entry);
11604 } else if (entry && *entry) {
11605 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11606 g_free(newmsginfo->cc);
11607 newmsginfo->cc = tmp;
11610 if ( strcasecmp(header,
11611 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11612 if ( newmsginfo->newsgroups == NULL ) {
11613 newmsginfo->newsgroups = g_strdup(entry);
11614 } else if (entry && *entry) {
11615 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11616 g_free(newmsginfo->newsgroups);
11617 newmsginfo->newsgroups = tmp;
11625 /* other data is unset */
11631 /* update compose's dictionaries from folder dict settings */
11632 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11633 FolderItem *folder_item)
11635 cm_return_if_fail(compose != NULL);
11637 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11638 FolderItemPrefs *prefs = folder_item->prefs;
11640 if (prefs->enable_default_dictionary)
11641 gtkaspell_change_dict(compose->gtkaspell,
11642 prefs->default_dictionary, FALSE);
11643 if (folder_item->prefs->enable_default_alt_dictionary)
11644 gtkaspell_change_alt_dict(compose->gtkaspell,
11645 prefs->default_alt_dictionary);
11646 if (prefs->enable_default_dictionary
11647 || prefs->enable_default_alt_dictionary)
11648 compose_spell_menu_changed(compose);
11653 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11655 Compose *compose = (Compose *)data;
11657 cm_return_if_fail(compose != NULL);
11659 gtk_widget_grab_focus(compose->text);