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);
989 if (mailto_account == NULL) {
991 Xstrdup_a(tmp_from, mailto_from, return NULL);
992 extract_address(tmp_from);
993 mailto_account = account_find_from_address(tmp_from, TRUE);
997 account = mailto_account;
1000 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1001 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1002 account = account_find_from_id(item->prefs->default_account);
1004 /* if no account prefs set, fallback to the current one */
1005 if (!account) account = cur_account;
1006 cm_return_val_if_fail(account != NULL, NULL);
1008 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1010 /* override from name if mailto asked for it */
1012 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1013 g_free(mailto_from);
1015 /* override from name according to folder properties */
1016 if (item && item->prefs &&
1017 item->prefs->compose_with_format &&
1018 item->prefs->compose_override_from_format &&
1019 *item->prefs->compose_override_from_format != '\0') {
1024 dummyinfo = compose_msginfo_new_from_compose(compose);
1026 /* decode \-escape sequences in the internal representation of the quote format */
1027 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1028 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1031 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1032 compose->gtkaspell);
1034 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1036 quote_fmt_scan_string(tmp);
1039 buf = quote_fmt_get_buffer();
1041 alertpanel_error(_("New message From format error."));
1043 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1044 quote_fmt_reset_vartable();
1049 compose->replyinfo = NULL;
1050 compose->fwdinfo = NULL;
1052 textview = GTK_TEXT_VIEW(compose->text);
1053 textbuf = gtk_text_view_get_buffer(textview);
1054 compose_create_tags(textview, compose);
1056 undo_block(compose->undostruct);
1058 compose_set_dictionaries_from_folder_prefs(compose, item);
1061 if (account->auto_sig)
1062 compose_insert_sig(compose, FALSE);
1063 gtk_text_buffer_get_start_iter(textbuf, &iter);
1064 gtk_text_buffer_place_cursor(textbuf, &iter);
1066 if (account->protocol != A_NNTP) {
1067 if (mailto && *mailto != '\0') {
1068 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1071 compose_set_folder_prefs(compose, item, TRUE);
1073 if (item && item->ret_rcpt) {
1074 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1077 if (mailto && *mailto != '\0') {
1078 if (!strchr(mailto, '@'))
1079 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1081 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1082 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1083 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1084 mfield = TO_FIELD_PRESENT;
1087 * CLAWS: just don't allow return receipt request, even if the user
1088 * may want to send an email. simple but foolproof.
1090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1092 compose_add_field_list( compose, listAddress );
1094 if (item && item->prefs && item->prefs->compose_with_format) {
1095 subject_format = item->prefs->compose_subject_format;
1096 body_format = item->prefs->compose_body_format;
1097 } else if (account->compose_with_format) {
1098 subject_format = account->compose_subject_format;
1099 body_format = account->compose_body_format;
1100 } else if (prefs_common.compose_with_format) {
1101 subject_format = prefs_common.compose_subject_format;
1102 body_format = prefs_common.compose_body_format;
1105 if (subject_format || body_format) {
1108 && *subject_format != '\0' )
1110 gchar *subject = NULL;
1115 dummyinfo = compose_msginfo_new_from_compose(compose);
1117 /* decode \-escape sequences in the internal representation of the quote format */
1118 tmp = g_malloc(strlen(subject_format)+1);
1119 pref_get_unescaped_pref(tmp, subject_format);
1121 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1123 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1124 compose->gtkaspell);
1126 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1128 quote_fmt_scan_string(tmp);
1131 buf = quote_fmt_get_buffer();
1133 alertpanel_error(_("New message subject format error."));
1135 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1136 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1137 quote_fmt_reset_vartable();
1141 mfield = SUBJECT_FIELD_PRESENT;
1145 && *body_format != '\0' )
1148 GtkTextBuffer *buffer;
1149 GtkTextIter start, end;
1153 dummyinfo = compose_msginfo_new_from_compose(compose);
1155 text = GTK_TEXT_VIEW(compose->text);
1156 buffer = gtk_text_view_get_buffer(text);
1157 gtk_text_buffer_get_start_iter(buffer, &start);
1158 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1159 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1161 compose_quote_fmt(compose, dummyinfo,
1163 NULL, tmp, FALSE, TRUE,
1164 _("The body of the \"New message\" template has an error at line %d."));
1165 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1166 quote_fmt_reset_vartable();
1170 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1171 gtkaspell_highlight_all(compose->gtkaspell);
1173 mfield = BODY_FIELD_PRESENT;
1177 procmsg_msginfo_free( dummyinfo );
1183 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1184 ainfo = (AttachInfo *) curr->data;
1185 compose_attach_append(compose, ainfo->file, ainfo->name,
1186 ainfo->content_type, ainfo->charset);
1190 compose_show_first_last_header(compose, TRUE);
1192 /* Set save folder */
1193 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1194 gchar *folderidentifier;
1196 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1197 folderidentifier = folder_item_get_identifier(item);
1198 compose_set_save_to(compose, folderidentifier);
1199 g_free(folderidentifier);
1202 /* Place cursor according to provided input (mfield) */
1204 case NO_FIELD_PRESENT:
1205 if (compose->header_last)
1206 gtk_widget_grab_focus(compose->header_last->entry);
1208 case TO_FIELD_PRESENT:
1209 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1211 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1214 gtk_widget_grab_focus(compose->subject_entry);
1216 case SUBJECT_FIELD_PRESENT:
1217 textview = GTK_TEXT_VIEW(compose->text);
1220 textbuf = gtk_text_view_get_buffer(textview);
1223 mark = gtk_text_buffer_get_insert(textbuf);
1224 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1225 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1227 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1228 * only defers where it comes to the variable body
1229 * is not null. If no body is present compose->text
1230 * will be null in which case you cannot place the
1231 * cursor inside the component so. An empty component
1232 * is therefore created before placing the cursor
1234 case BODY_FIELD_PRESENT:
1235 cursor_pos = quote_fmt_get_cursor_pos();
1236 if (cursor_pos == -1)
1237 gtk_widget_grab_focus(compose->header_last->entry);
1239 gtk_widget_grab_focus(compose->text);
1243 undo_unblock(compose->undostruct);
1245 if (prefs_common.auto_exteditor)
1246 compose_exec_ext_editor(compose);
1248 compose->draft_timeout_tag = -1;
1249 SCROLL_TO_CURSOR(compose);
1251 compose->modified = FALSE;
1252 compose_set_title(compose);
1254 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1259 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1260 gboolean override_pref, const gchar *system)
1262 const gchar *privacy = NULL;
1264 cm_return_if_fail(compose != NULL);
1265 cm_return_if_fail(account != NULL);
1267 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1272 else if (account->default_privacy_system
1273 && strlen(account->default_privacy_system)) {
1274 privacy = account->default_privacy_system;
1276 GSList *privacy_avail = privacy_get_system_ids();
1277 if (privacy_avail && g_slist_length(privacy_avail)) {
1278 privacy = (gchar *)(privacy_avail->data);
1281 if (privacy != NULL) {
1283 g_free(compose->privacy_system);
1284 compose->privacy_system = NULL;
1286 if (compose->privacy_system == NULL)
1287 compose->privacy_system = g_strdup(privacy);
1288 else if (*(compose->privacy_system) == '\0') {
1289 g_free(compose->privacy_system);
1290 compose->privacy_system = g_strdup(privacy);
1292 compose_update_privacy_system_menu_item(compose, FALSE);
1293 compose_use_encryption(compose, TRUE);
1297 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1299 const gchar *privacy = NULL;
1303 else if (account->default_privacy_system
1304 && strlen(account->default_privacy_system)) {
1305 privacy = account->default_privacy_system;
1307 GSList *privacy_avail = privacy_get_system_ids();
1308 if (privacy_avail && g_slist_length(privacy_avail)) {
1309 privacy = (gchar *)(privacy_avail->data);
1313 if (privacy != NULL) {
1315 g_free(compose->privacy_system);
1316 compose->privacy_system = NULL;
1318 if (compose->privacy_system == NULL)
1319 compose->privacy_system = g_strdup(privacy);
1320 compose_update_privacy_system_menu_item(compose, FALSE);
1321 compose_use_signing(compose, TRUE);
1325 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1329 Compose *compose = NULL;
1331 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1333 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1334 cm_return_val_if_fail(msginfo != NULL, NULL);
1336 list_len = g_slist_length(msginfo_list);
1340 case COMPOSE_REPLY_TO_ADDRESS:
1341 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1342 FALSE, prefs_common.default_reply_list, FALSE, body);
1344 case COMPOSE_REPLY_WITH_QUOTE:
1345 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1346 FALSE, prefs_common.default_reply_list, FALSE, body);
1348 case COMPOSE_REPLY_WITHOUT_QUOTE:
1349 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1350 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1352 case COMPOSE_REPLY_TO_SENDER:
1353 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1354 FALSE, FALSE, TRUE, body);
1356 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1357 compose = compose_followup_and_reply_to(msginfo,
1358 COMPOSE_QUOTE_CHECK,
1359 FALSE, FALSE, body);
1361 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1362 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1363 FALSE, FALSE, TRUE, body);
1365 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1367 FALSE, FALSE, TRUE, NULL);
1369 case COMPOSE_REPLY_TO_ALL:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1371 TRUE, FALSE, FALSE, body);
1373 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1375 TRUE, FALSE, FALSE, body);
1377 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1379 TRUE, FALSE, FALSE, NULL);
1381 case COMPOSE_REPLY_TO_LIST:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1383 FALSE, TRUE, FALSE, body);
1385 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1386 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1387 FALSE, TRUE, FALSE, body);
1389 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1390 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1391 FALSE, TRUE, FALSE, NULL);
1393 case COMPOSE_FORWARD:
1394 if (prefs_common.forward_as_attachment) {
1395 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1398 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1402 case COMPOSE_FORWARD_INLINE:
1403 /* check if we reply to more than one Message */
1404 if (list_len == 1) {
1405 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1408 /* more messages FALL THROUGH */
1409 case COMPOSE_FORWARD_AS_ATTACH:
1410 compose = compose_forward_multiple(NULL, msginfo_list);
1412 case COMPOSE_REDIRECT:
1413 compose = compose_redirect(NULL, msginfo, FALSE);
1416 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1419 if (compose == NULL) {
1420 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1424 compose->rmode = mode;
1425 switch (compose->rmode) {
1427 case COMPOSE_REPLY_WITH_QUOTE:
1428 case COMPOSE_REPLY_WITHOUT_QUOTE:
1429 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1430 debug_print("reply mode Normal\n");
1431 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1432 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1434 case COMPOSE_REPLY_TO_SENDER:
1435 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1436 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1437 debug_print("reply mode Sender\n");
1438 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1440 case COMPOSE_REPLY_TO_ALL:
1441 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1442 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1443 debug_print("reply mode All\n");
1444 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1446 case COMPOSE_REPLY_TO_LIST:
1447 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1448 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1449 debug_print("reply mode List\n");
1450 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1452 case COMPOSE_REPLY_TO_ADDRESS:
1453 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1461 static Compose *compose_reply(MsgInfo *msginfo,
1462 ComposeQuoteMode quote_mode,
1468 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1469 to_sender, FALSE, body);
1472 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1473 ComposeQuoteMode quote_mode,
1478 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1479 to_sender, TRUE, body);
1482 static void compose_extract_original_charset(Compose *compose)
1484 MsgInfo *info = NULL;
1485 if (compose->replyinfo) {
1486 info = compose->replyinfo;
1487 } else if (compose->fwdinfo) {
1488 info = compose->fwdinfo;
1489 } else if (compose->targetinfo) {
1490 info = compose->targetinfo;
1493 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1494 MimeInfo *partinfo = mimeinfo;
1495 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1496 partinfo = procmime_mimeinfo_next(partinfo);
1498 compose->orig_charset =
1499 g_strdup(procmime_mimeinfo_get_parameter(
1500 partinfo, "charset"));
1502 procmime_mimeinfo_free_all(mimeinfo);
1506 #define SIGNAL_BLOCK(buffer) { \
1507 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1508 G_CALLBACK(compose_changed_cb), \
1510 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1511 G_CALLBACK(text_inserted), \
1515 #define SIGNAL_UNBLOCK(buffer) { \
1516 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1517 G_CALLBACK(compose_changed_cb), \
1519 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1520 G_CALLBACK(text_inserted), \
1524 static Compose *compose_generic_reply(MsgInfo *msginfo,
1525 ComposeQuoteMode quote_mode,
1526 gboolean to_all, gboolean to_ml,
1528 gboolean followup_and_reply_to,
1532 PrefsAccount *account = NULL;
1533 GtkTextView *textview;
1534 GtkTextBuffer *textbuf;
1535 gboolean quote = FALSE;
1536 const gchar *qmark = NULL;
1537 const gchar *body_fmt = NULL;
1538 gchar *s_system = NULL;
1540 cm_return_val_if_fail(msginfo != NULL, NULL);
1541 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1543 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1545 cm_return_val_if_fail(account != NULL, NULL);
1547 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1549 compose->updating = TRUE;
1551 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1554 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1555 if (!compose->replyinfo)
1556 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1558 compose_extract_original_charset(compose);
1560 if (msginfo->folder && msginfo->folder->ret_rcpt)
1561 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1563 /* Set save folder */
1564 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1565 gchar *folderidentifier;
1567 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1568 folderidentifier = folder_item_get_identifier(msginfo->folder);
1569 compose_set_save_to(compose, folderidentifier);
1570 g_free(folderidentifier);
1573 if (compose_parse_header(compose, msginfo) < 0) {
1574 compose->updating = FALSE;
1575 compose_destroy(compose);
1579 /* override from name according to folder properties */
1580 if (msginfo->folder && msginfo->folder->prefs &&
1581 msginfo->folder->prefs->reply_with_format &&
1582 msginfo->folder->prefs->reply_override_from_format &&
1583 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1588 /* decode \-escape sequences in the internal representation of the quote format */
1589 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1590 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1593 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1594 compose->gtkaspell);
1596 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1598 quote_fmt_scan_string(tmp);
1601 buf = quote_fmt_get_buffer();
1603 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1605 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1606 quote_fmt_reset_vartable();
1611 textview = (GTK_TEXT_VIEW(compose->text));
1612 textbuf = gtk_text_view_get_buffer(textview);
1613 compose_create_tags(textview, compose);
1615 undo_block(compose->undostruct);
1617 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1618 gtkaspell_block_check(compose->gtkaspell);
1621 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1622 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1623 /* use the reply format of folder (if enabled), or the account's one
1624 (if enabled) or fallback to the global reply format, which is always
1625 enabled (even if empty), and use the relevant quotemark */
1627 if (msginfo->folder && msginfo->folder->prefs &&
1628 msginfo->folder->prefs->reply_with_format) {
1629 qmark = msginfo->folder->prefs->reply_quotemark;
1630 body_fmt = msginfo->folder->prefs->reply_body_format;
1632 } else if (account->reply_with_format) {
1633 qmark = account->reply_quotemark;
1634 body_fmt = account->reply_body_format;
1637 qmark = prefs_common.quotemark;
1638 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1639 body_fmt = gettext(prefs_common.quotefmt);
1646 /* empty quotemark is not allowed */
1647 if (qmark == NULL || *qmark == '\0')
1649 compose_quote_fmt(compose, compose->replyinfo,
1650 body_fmt, qmark, body, FALSE, TRUE,
1651 _("The body of the \"Reply\" template has an error at line %d."));
1652 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1653 quote_fmt_reset_vartable();
1656 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1657 compose_force_encryption(compose, account, FALSE, s_system);
1660 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1661 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1662 compose_force_signing(compose, account, s_system);
1666 SIGNAL_BLOCK(textbuf);
1668 if (account->auto_sig)
1669 compose_insert_sig(compose, FALSE);
1671 compose_wrap_all(compose);
1674 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1675 gtkaspell_highlight_all(compose->gtkaspell);
1676 gtkaspell_unblock_check(compose->gtkaspell);
1678 SIGNAL_UNBLOCK(textbuf);
1680 gtk_widget_grab_focus(compose->text);
1682 undo_unblock(compose->undostruct);
1684 if (prefs_common.auto_exteditor)
1685 compose_exec_ext_editor(compose);
1687 compose->modified = FALSE;
1688 compose_set_title(compose);
1690 compose->updating = FALSE;
1691 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1692 SCROLL_TO_CURSOR(compose);
1694 if (compose->deferred_destroy) {
1695 compose_destroy(compose);
1703 #define INSERT_FW_HEADER(var, hdr) \
1704 if (msginfo->var && *msginfo->var) { \
1705 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1706 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1707 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1710 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1711 gboolean as_attach, const gchar *body,
1712 gboolean no_extedit,
1716 GtkTextView *textview;
1717 GtkTextBuffer *textbuf;
1718 gint cursor_pos = -1;
1721 cm_return_val_if_fail(msginfo != NULL, NULL);
1722 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1725 !(account = compose_guess_forward_account_from_msginfo
1727 account = cur_account;
1729 if (!prefs_common.forward_as_attachment)
1730 mode = COMPOSE_FORWARD_INLINE;
1732 mode = COMPOSE_FORWARD;
1733 compose = compose_create(account, msginfo->folder, mode, batch);
1735 compose->updating = TRUE;
1736 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1737 if (!compose->fwdinfo)
1738 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1740 compose_extract_original_charset(compose);
1742 if (msginfo->subject && *msginfo->subject) {
1743 gchar *buf, *buf2, *p;
1745 buf = p = g_strdup(msginfo->subject);
1746 p += subject_get_prefix_length(p);
1747 memmove(buf, p, strlen(p) + 1);
1749 buf2 = g_strdup_printf("Fw: %s", buf);
1750 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1756 /* override from name according to folder properties */
1757 if (msginfo->folder && msginfo->folder->prefs &&
1758 msginfo->folder->prefs->forward_with_format &&
1759 msginfo->folder->prefs->forward_override_from_format &&
1760 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1764 MsgInfo *full_msginfo = NULL;
1767 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1769 full_msginfo = procmsg_msginfo_copy(msginfo);
1771 /* decode \-escape sequences in the internal representation of the quote format */
1772 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1773 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1776 gtkaspell_block_check(compose->gtkaspell);
1777 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1778 compose->gtkaspell);
1780 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1782 quote_fmt_scan_string(tmp);
1785 buf = quote_fmt_get_buffer();
1787 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1789 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1790 quote_fmt_reset_vartable();
1793 procmsg_msginfo_free(full_msginfo);
1796 textview = GTK_TEXT_VIEW(compose->text);
1797 textbuf = gtk_text_view_get_buffer(textview);
1798 compose_create_tags(textview, compose);
1800 undo_block(compose->undostruct);
1804 msgfile = procmsg_get_message_file(msginfo);
1805 if (!is_file_exist(msgfile))
1806 g_warning("%s: file not exist\n", msgfile);
1808 compose_attach_append(compose, msgfile, msgfile,
1809 "message/rfc822", NULL);
1813 const gchar *qmark = NULL;
1814 const gchar *body_fmt = NULL;
1815 MsgInfo *full_msginfo;
1817 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1819 full_msginfo = procmsg_msginfo_copy(msginfo);
1821 /* use the forward format of folder (if enabled), or the account's one
1822 (if enabled) or fallback to the global forward format, which is always
1823 enabled (even if empty), and use the relevant quotemark */
1824 if (msginfo->folder && msginfo->folder->prefs &&
1825 msginfo->folder->prefs->forward_with_format) {
1826 qmark = msginfo->folder->prefs->forward_quotemark;
1827 body_fmt = msginfo->folder->prefs->forward_body_format;
1829 } else if (account->forward_with_format) {
1830 qmark = account->forward_quotemark;
1831 body_fmt = account->forward_body_format;
1834 qmark = prefs_common.fw_quotemark;
1835 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1836 body_fmt = gettext(prefs_common.fw_quotefmt);
1841 /* empty quotemark is not allowed */
1842 if (qmark == NULL || *qmark == '\0')
1845 compose_quote_fmt(compose, full_msginfo,
1846 body_fmt, qmark, body, FALSE, TRUE,
1847 _("The body of the \"Forward\" template has an error at line %d."));
1848 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1849 quote_fmt_reset_vartable();
1850 compose_attach_parts(compose, msginfo);
1852 procmsg_msginfo_free(full_msginfo);
1855 SIGNAL_BLOCK(textbuf);
1857 if (account->auto_sig)
1858 compose_insert_sig(compose, FALSE);
1860 compose_wrap_all(compose);
1863 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1864 gtkaspell_highlight_all(compose->gtkaspell);
1865 gtkaspell_unblock_check(compose->gtkaspell);
1867 SIGNAL_UNBLOCK(textbuf);
1869 cursor_pos = quote_fmt_get_cursor_pos();
1870 if (cursor_pos == -1)
1871 gtk_widget_grab_focus(compose->header_last->entry);
1873 gtk_widget_grab_focus(compose->text);
1875 if (!no_extedit && prefs_common.auto_exteditor)
1876 compose_exec_ext_editor(compose);
1879 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1880 gchar *folderidentifier;
1882 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1883 folderidentifier = folder_item_get_identifier(msginfo->folder);
1884 compose_set_save_to(compose, folderidentifier);
1885 g_free(folderidentifier);
1888 undo_unblock(compose->undostruct);
1890 compose->modified = FALSE;
1891 compose_set_title(compose);
1893 compose->updating = FALSE;
1894 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1895 SCROLL_TO_CURSOR(compose);
1897 if (compose->deferred_destroy) {
1898 compose_destroy(compose);
1902 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1907 #undef INSERT_FW_HEADER
1909 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1912 GtkTextView *textview;
1913 GtkTextBuffer *textbuf;
1917 gboolean single_mail = TRUE;
1919 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1921 if (g_slist_length(msginfo_list) > 1)
1922 single_mail = FALSE;
1924 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1925 if (((MsgInfo *)msginfo->data)->folder == NULL)
1928 /* guess account from first selected message */
1930 !(account = compose_guess_forward_account_from_msginfo
1931 (msginfo_list->data)))
1932 account = cur_account;
1934 cm_return_val_if_fail(account != NULL, NULL);
1936 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1937 if (msginfo->data) {
1938 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1939 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1943 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1944 g_warning("no msginfo_list");
1948 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1950 compose->updating = TRUE;
1952 /* override from name according to folder properties */
1953 if (msginfo_list->data) {
1954 MsgInfo *msginfo = msginfo_list->data;
1956 if (msginfo->folder && msginfo->folder->prefs &&
1957 msginfo->folder->prefs->forward_with_format &&
1958 msginfo->folder->prefs->forward_override_from_format &&
1959 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1964 /* decode \-escape sequences in the internal representation of the quote format */
1965 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1966 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1969 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1970 compose->gtkaspell);
1972 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1974 quote_fmt_scan_string(tmp);
1977 buf = quote_fmt_get_buffer();
1979 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1981 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1982 quote_fmt_reset_vartable();
1988 textview = GTK_TEXT_VIEW(compose->text);
1989 textbuf = gtk_text_view_get_buffer(textview);
1990 compose_create_tags(textview, compose);
1992 undo_block(compose->undostruct);
1993 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1994 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1996 if (!is_file_exist(msgfile))
1997 g_warning("%s: file not exist\n", msgfile);
1999 compose_attach_append(compose, msgfile, msgfile,
2000 "message/rfc822", NULL);
2005 MsgInfo *info = (MsgInfo *)msginfo_list->data;
2006 if (info->subject && *info->subject) {
2007 gchar *buf, *buf2, *p;
2009 buf = p = g_strdup(info->subject);
2010 p += subject_get_prefix_length(p);
2011 memmove(buf, p, strlen(p) + 1);
2013 buf2 = g_strdup_printf("Fw: %s", buf);
2014 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2020 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2021 _("Fw: multiple emails"));
2024 SIGNAL_BLOCK(textbuf);
2026 if (account->auto_sig)
2027 compose_insert_sig(compose, FALSE);
2029 compose_wrap_all(compose);
2031 SIGNAL_UNBLOCK(textbuf);
2033 gtk_text_buffer_get_start_iter(textbuf, &iter);
2034 gtk_text_buffer_place_cursor(textbuf, &iter);
2036 gtk_widget_grab_focus(compose->header_last->entry);
2037 undo_unblock(compose->undostruct);
2038 compose->modified = FALSE;
2039 compose_set_title(compose);
2041 compose->updating = FALSE;
2042 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2043 SCROLL_TO_CURSOR(compose);
2045 if (compose->deferred_destroy) {
2046 compose_destroy(compose);
2050 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2055 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2057 GtkTextIter start = *iter;
2058 GtkTextIter end_iter;
2059 int start_pos = gtk_text_iter_get_offset(&start);
2061 if (!compose->account->sig_sep)
2064 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2065 start_pos+strlen(compose->account->sig_sep));
2067 /* check sig separator */
2068 str = gtk_text_iter_get_text(&start, &end_iter);
2069 if (!strcmp(str, compose->account->sig_sep)) {
2071 /* check end of line (\n) */
2072 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2073 start_pos+strlen(compose->account->sig_sep));
2074 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2075 start_pos+strlen(compose->account->sig_sep)+1);
2076 tmp = gtk_text_iter_get_text(&start, &end_iter);
2077 if (!strcmp(tmp,"\n")) {
2089 static void compose_colorize_signature(Compose *compose)
2091 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2093 GtkTextIter end_iter;
2094 gtk_text_buffer_get_start_iter(buffer, &iter);
2095 while (gtk_text_iter_forward_line(&iter))
2096 if (compose_is_sig_separator(compose, buffer, &iter)) {
2097 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2098 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2102 #define BLOCK_WRAP() { \
2103 prev_autowrap = compose->autowrap; \
2104 buffer = gtk_text_view_get_buffer( \
2105 GTK_TEXT_VIEW(compose->text)); \
2106 compose->autowrap = FALSE; \
2108 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2109 G_CALLBACK(compose_changed_cb), \
2111 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2112 G_CALLBACK(text_inserted), \
2115 #define UNBLOCK_WRAP() { \
2116 compose->autowrap = prev_autowrap; \
2117 if (compose->autowrap) { \
2118 gint old = compose->draft_timeout_tag; \
2119 compose->draft_timeout_tag = -2; \
2120 compose_wrap_all(compose); \
2121 compose->draft_timeout_tag = old; \
2124 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2125 G_CALLBACK(compose_changed_cb), \
2127 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2128 G_CALLBACK(text_inserted), \
2132 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2134 Compose *compose = NULL;
2135 PrefsAccount *account = NULL;
2136 GtkTextView *textview;
2137 GtkTextBuffer *textbuf;
2141 gchar buf[BUFFSIZE];
2142 gboolean use_signing = FALSE;
2143 gboolean use_encryption = FALSE;
2144 gchar *privacy_system = NULL;
2145 int priority = PRIORITY_NORMAL;
2146 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2147 gboolean autowrap = prefs_common.autowrap;
2148 gboolean autoindent = prefs_common.auto_indent;
2149 HeaderEntry *manual_headers = NULL;
2151 cm_return_val_if_fail(msginfo != NULL, NULL);
2152 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2154 if (compose_put_existing_to_front(msginfo)) {
2158 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2159 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2160 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2161 gchar queueheader_buf[BUFFSIZE];
2164 /* Select Account from queue headers */
2165 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2166 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2167 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2168 account = account_find_from_id(id);
2170 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2171 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2172 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2173 account = account_find_from_id(id);
2175 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2176 sizeof(queueheader_buf), "NAID:")) {
2177 id = atoi(&queueheader_buf[strlen("NAID:")]);
2178 account = account_find_from_id(id);
2180 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2181 sizeof(queueheader_buf), "MAID:")) {
2182 id = atoi(&queueheader_buf[strlen("MAID:")]);
2183 account = account_find_from_id(id);
2185 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2186 sizeof(queueheader_buf), "S:")) {
2187 account = account_find_from_address(queueheader_buf, FALSE);
2189 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2190 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2191 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2192 use_signing = param;
2195 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2196 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2197 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2198 use_signing = param;
2201 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2202 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2203 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2204 use_encryption = param;
2206 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2207 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2208 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2209 use_encryption = param;
2211 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2212 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2213 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2216 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2217 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2218 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2221 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2222 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2223 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2225 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2226 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2227 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2229 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2230 sizeof(queueheader_buf), "X-Priority: ")) {
2231 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2234 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2235 sizeof(queueheader_buf), "RMID:")) {
2236 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2237 if (tokens[0] && tokens[1] && tokens[2]) {
2238 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2239 if (orig_item != NULL) {
2240 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2245 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2246 sizeof(queueheader_buf), "FMID:")) {
2247 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2248 if (tokens[0] && tokens[1] && tokens[2]) {
2249 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2250 if (orig_item != NULL) {
2251 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2256 /* Get manual headers */
2257 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2258 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2259 if (*listmh != '\0') {
2260 debug_print("Got manual headers: %s\n", listmh);
2261 manual_headers = procheader_entries_from_str(listmh);
2266 account = msginfo->folder->folder->account;
2269 if (!account && prefs_common.reedit_account_autosel) {
2270 gchar from[BUFFSIZE];
2271 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2272 extract_address(from);
2273 account = account_find_from_address(from, FALSE);
2277 account = cur_account;
2279 cm_return_val_if_fail(account != NULL, NULL);
2281 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2283 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2284 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2285 compose->autowrap = autowrap;
2286 compose->replyinfo = replyinfo;
2287 compose->fwdinfo = fwdinfo;
2289 compose->updating = TRUE;
2290 compose->priority = priority;
2292 if (privacy_system != NULL) {
2293 compose->privacy_system = privacy_system;
2294 compose_use_signing(compose, use_signing);
2295 compose_use_encryption(compose, use_encryption);
2296 compose_update_privacy_system_menu_item(compose, FALSE);
2298 activate_privacy_system(compose, account, FALSE);
2301 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2303 compose_extract_original_charset(compose);
2305 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2306 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2307 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2308 gchar queueheader_buf[BUFFSIZE];
2310 /* Set message save folder */
2311 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2312 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2313 compose_set_save_to(compose, &queueheader_buf[4]);
2315 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2316 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2318 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2323 if (compose_parse_header(compose, msginfo) < 0) {
2324 compose->updating = FALSE;
2325 compose_destroy(compose);
2328 compose_reedit_set_entry(compose, msginfo);
2330 textview = GTK_TEXT_VIEW(compose->text);
2331 textbuf = gtk_text_view_get_buffer(textview);
2332 compose_create_tags(textview, compose);
2334 mark = gtk_text_buffer_get_insert(textbuf);
2335 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2337 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2338 G_CALLBACK(compose_changed_cb),
2341 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2342 fp = procmime_get_first_encrypted_text_content(msginfo);
2344 compose_force_encryption(compose, account, TRUE, NULL);
2347 fp = procmime_get_first_text_content(msginfo);
2350 g_warning("Can't get text part\n");
2354 gboolean prev_autowrap;
2355 GtkTextBuffer *buffer;
2357 while (fgets(buf, sizeof(buf), fp) != NULL) {
2359 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2365 compose_attach_parts(compose, msginfo);
2367 compose_colorize_signature(compose);
2369 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2370 G_CALLBACK(compose_changed_cb),
2373 if (manual_headers != NULL) {
2374 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2375 procheader_entries_free(manual_headers);
2376 compose->updating = FALSE;
2377 compose_destroy(compose);
2380 procheader_entries_free(manual_headers);
2383 gtk_widget_grab_focus(compose->text);
2385 if (prefs_common.auto_exteditor) {
2386 compose_exec_ext_editor(compose);
2388 compose->modified = FALSE;
2389 compose_set_title(compose);
2391 compose->updating = FALSE;
2392 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2393 SCROLL_TO_CURSOR(compose);
2395 if (compose->deferred_destroy) {
2396 compose_destroy(compose);
2400 compose->sig_str = account_get_signature_str(compose->account);
2402 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2407 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2414 cm_return_val_if_fail(msginfo != NULL, NULL);
2417 account = account_get_reply_account(msginfo,
2418 prefs_common.reply_account_autosel);
2419 cm_return_val_if_fail(account != NULL, NULL);
2421 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2423 compose->updating = TRUE;
2425 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2426 compose->replyinfo = NULL;
2427 compose->fwdinfo = NULL;
2429 compose_show_first_last_header(compose, TRUE);
2431 gtk_widget_grab_focus(compose->header_last->entry);
2433 filename = procmsg_get_message_file(msginfo);
2435 if (filename == NULL) {
2436 compose->updating = FALSE;
2437 compose_destroy(compose);
2442 compose->redirect_filename = filename;
2444 /* Set save folder */
2445 item = msginfo->folder;
2446 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2447 gchar *folderidentifier;
2449 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2450 folderidentifier = folder_item_get_identifier(item);
2451 compose_set_save_to(compose, folderidentifier);
2452 g_free(folderidentifier);
2455 compose_attach_parts(compose, msginfo);
2457 if (msginfo->subject)
2458 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2460 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2462 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2463 _("The body of the \"Redirect\" template has an error at line %d."));
2464 quote_fmt_reset_vartable();
2465 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2467 compose_colorize_signature(compose);
2470 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2471 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2472 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2475 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2476 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2477 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2478 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2479 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2480 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2481 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2483 if (compose->toolbar->draft_btn)
2484 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2485 if (compose->toolbar->insert_btn)
2486 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2487 if (compose->toolbar->attach_btn)
2488 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2489 if (compose->toolbar->sig_btn)
2490 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2491 if (compose->toolbar->exteditor_btn)
2492 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2493 if (compose->toolbar->linewrap_current_btn)
2494 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2495 if (compose->toolbar->linewrap_all_btn)
2496 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2498 compose->modified = FALSE;
2499 compose_set_title(compose);
2500 compose->updating = FALSE;
2501 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2502 SCROLL_TO_CURSOR(compose);
2504 if (compose->deferred_destroy) {
2505 compose_destroy(compose);
2509 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2514 GList *compose_get_compose_list(void)
2516 return compose_list;
2519 void compose_entry_append(Compose *compose, const gchar *address,
2520 ComposeEntryType type, ComposePrefType pref_type)
2522 const gchar *header;
2524 gboolean in_quote = FALSE;
2525 if (!address || *address == '\0') return;
2532 header = N_("Bcc:");
2534 case COMPOSE_REPLYTO:
2535 header = N_("Reply-To:");
2537 case COMPOSE_NEWSGROUPS:
2538 header = N_("Newsgroups:");
2540 case COMPOSE_FOLLOWUPTO:
2541 header = N_( "Followup-To:");
2543 case COMPOSE_INREPLYTO:
2544 header = N_( "In-Reply-To:");
2551 header = prefs_common_translated_header_name(header);
2553 cur = begin = (gchar *)address;
2555 /* we separate the line by commas, but not if we're inside a quoted
2557 while (*cur != '\0') {
2559 in_quote = !in_quote;
2560 if (*cur == ',' && !in_quote) {
2561 gchar *tmp = g_strdup(begin);
2563 tmp[cur-begin]='\0';
2566 while (*tmp == ' ' || *tmp == '\t')
2568 compose_add_header_entry(compose, header, tmp, pref_type);
2575 gchar *tmp = g_strdup(begin);
2577 tmp[cur-begin]='\0';
2578 while (*tmp == ' ' || *tmp == '\t')
2580 compose_add_header_entry(compose, header, tmp, pref_type);
2585 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2587 #if !GTK_CHECK_VERSION(3, 0, 0)
2588 static GdkColor yellow;
2589 static GdkColor black;
2590 static gboolean yellow_initialised = FALSE;
2592 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2593 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2598 #if !GTK_CHECK_VERSION(3, 0, 0)
2599 if (!yellow_initialised) {
2600 gdk_color_parse("#f5f6be", &yellow);
2601 gdk_color_parse("#000000", &black);
2602 yellow_initialised = gdk_colormap_alloc_color(
2603 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2604 yellow_initialised &= gdk_colormap_alloc_color(
2605 gdk_colormap_get_system(), &black, FALSE, TRUE);
2609 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2610 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2611 if (gtk_entry_get_text(entry) &&
2612 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2613 #if !GTK_CHECK_VERSION(3, 0, 0)
2614 if (yellow_initialised) {
2616 gtk_widget_modify_base(
2617 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2618 GTK_STATE_NORMAL, &yellow);
2619 gtk_widget_modify_text(
2620 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2621 GTK_STATE_NORMAL, &black);
2622 #if !GTK_CHECK_VERSION(3, 0, 0)
2629 void compose_toolbar_cb(gint action, gpointer data)
2631 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2632 Compose *compose = (Compose*)toolbar_item->parent;
2634 cm_return_if_fail(compose != NULL);
2638 compose_send_cb(NULL, compose);
2641 compose_send_later_cb(NULL, compose);
2644 compose_draft(compose, COMPOSE_QUIT_EDITING);
2647 compose_insert_file_cb(NULL, compose);
2650 compose_attach_cb(NULL, compose);
2653 compose_insert_sig(compose, FALSE);
2656 compose_ext_editor_cb(NULL, compose);
2658 case A_LINEWRAP_CURRENT:
2659 compose_beautify_paragraph(compose, NULL, TRUE);
2661 case A_LINEWRAP_ALL:
2662 compose_wrap_all_full(compose, TRUE);
2665 compose_address_cb(NULL, compose);
2668 case A_CHECK_SPELLING:
2669 compose_check_all(NULL, compose);
2677 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2682 gchar *subject = NULL;
2686 gchar **attach = NULL;
2687 gchar *inreplyto = NULL;
2688 MailField mfield = NO_FIELD_PRESENT;
2690 /* get mailto parts but skip from */
2691 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2694 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2695 mfield = TO_FIELD_PRESENT;
2698 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2700 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2702 if (!g_utf8_validate (subject, -1, NULL)) {
2703 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2704 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2707 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2709 mfield = SUBJECT_FIELD_PRESENT;
2712 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2713 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2716 gboolean prev_autowrap = compose->autowrap;
2718 compose->autowrap = FALSE;
2720 mark = gtk_text_buffer_get_insert(buffer);
2721 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2723 if (!g_utf8_validate (body, -1, NULL)) {
2724 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2725 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2728 gtk_text_buffer_insert(buffer, &iter, body, -1);
2730 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2732 compose->autowrap = prev_autowrap;
2733 if (compose->autowrap)
2734 compose_wrap_all(compose);
2735 mfield = BODY_FIELD_PRESENT;
2739 gint i = 0, att = 0;
2740 gchar *warn_files = NULL;
2741 while (attach[i] != NULL) {
2742 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2743 if (utf8_filename) {
2744 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2745 gchar *tmp = g_strdup_printf("%s%s\n",
2746 warn_files?warn_files:"",
2752 g_free(utf8_filename);
2754 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2759 alertpanel_notice(ngettext(
2760 "The following file has been attached: \n%s",
2761 "The following files have been attached: \n%s", att), warn_files);
2766 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2779 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2781 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2782 {"Cc:", NULL, TRUE},
2783 {"References:", NULL, FALSE},
2784 {"Bcc:", NULL, TRUE},
2785 {"Newsgroups:", NULL, TRUE},
2786 {"Followup-To:", NULL, TRUE},
2787 {"List-Post:", NULL, FALSE},
2788 {"X-Priority:", NULL, FALSE},
2789 {NULL, NULL, FALSE}};
2805 cm_return_val_if_fail(msginfo != NULL, -1);
2807 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2808 procheader_get_header_fields(fp, hentry);
2811 if (hentry[H_REPLY_TO].body != NULL) {
2812 if (hentry[H_REPLY_TO].body[0] != '\0') {
2814 conv_unmime_header(hentry[H_REPLY_TO].body,
2817 g_free(hentry[H_REPLY_TO].body);
2818 hentry[H_REPLY_TO].body = NULL;
2820 if (hentry[H_CC].body != NULL) {
2821 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2822 g_free(hentry[H_CC].body);
2823 hentry[H_CC].body = NULL;
2825 if (hentry[H_REFERENCES].body != NULL) {
2826 if (compose->mode == COMPOSE_REEDIT)
2827 compose->references = hentry[H_REFERENCES].body;
2829 compose->references = compose_parse_references
2830 (hentry[H_REFERENCES].body, msginfo->msgid);
2831 g_free(hentry[H_REFERENCES].body);
2833 hentry[H_REFERENCES].body = NULL;
2835 if (hentry[H_BCC].body != NULL) {
2836 if (compose->mode == COMPOSE_REEDIT)
2838 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2839 g_free(hentry[H_BCC].body);
2840 hentry[H_BCC].body = NULL;
2842 if (hentry[H_NEWSGROUPS].body != NULL) {
2843 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2844 hentry[H_NEWSGROUPS].body = NULL;
2846 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2847 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2848 compose->followup_to =
2849 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2852 g_free(hentry[H_FOLLOWUP_TO].body);
2853 hentry[H_FOLLOWUP_TO].body = NULL;
2855 if (hentry[H_LIST_POST].body != NULL) {
2856 gchar *to = NULL, *start = NULL;
2858 extract_address(hentry[H_LIST_POST].body);
2859 if (hentry[H_LIST_POST].body[0] != '\0') {
2860 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2862 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2863 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2866 g_free(compose->ml_post);
2867 compose->ml_post = to;
2870 g_free(hentry[H_LIST_POST].body);
2871 hentry[H_LIST_POST].body = NULL;
2874 /* CLAWS - X-Priority */
2875 if (compose->mode == COMPOSE_REEDIT)
2876 if (hentry[H_X_PRIORITY].body != NULL) {
2879 priority = atoi(hentry[H_X_PRIORITY].body);
2880 g_free(hentry[H_X_PRIORITY].body);
2882 hentry[H_X_PRIORITY].body = NULL;
2884 if (priority < PRIORITY_HIGHEST ||
2885 priority > PRIORITY_LOWEST)
2886 priority = PRIORITY_NORMAL;
2888 compose->priority = priority;
2891 if (compose->mode == COMPOSE_REEDIT) {
2892 if (msginfo->inreplyto && *msginfo->inreplyto)
2893 compose->inreplyto = g_strdup(msginfo->inreplyto);
2897 if (msginfo->msgid && *msginfo->msgid)
2898 compose->inreplyto = g_strdup(msginfo->msgid);
2900 if (!compose->references) {
2901 if (msginfo->msgid && *msginfo->msgid) {
2902 if (msginfo->inreplyto && *msginfo->inreplyto)
2903 compose->references =
2904 g_strdup_printf("<%s>\n\t<%s>",
2908 compose->references =
2909 g_strconcat("<", msginfo->msgid, ">",
2911 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2912 compose->references =
2913 g_strconcat("<", msginfo->inreplyto, ">",
2921 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2926 cm_return_val_if_fail(msginfo != NULL, -1);
2928 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2929 procheader_get_header_fields(fp, entries);
2933 while (he != NULL && he->name != NULL) {
2935 GtkListStore *model = NULL;
2937 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2938 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2939 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2940 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2941 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2948 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2950 GSList *ref_id_list, *cur;
2954 ref_id_list = references_list_append(NULL, ref);
2955 if (!ref_id_list) return NULL;
2956 if (msgid && *msgid)
2957 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2962 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2963 /* "<" + Message-ID + ">" + CR+LF+TAB */
2964 len += strlen((gchar *)cur->data) + 5;
2966 if (len > MAX_REFERENCES_LEN) {
2967 /* remove second message-ID */
2968 if (ref_id_list && ref_id_list->next &&
2969 ref_id_list->next->next) {
2970 g_free(ref_id_list->next->data);
2971 ref_id_list = g_slist_remove
2972 (ref_id_list, ref_id_list->next->data);
2974 slist_free_strings_full(ref_id_list);
2981 new_ref = g_string_new("");
2982 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2983 if (new_ref->len > 0)
2984 g_string_append(new_ref, "\n\t");
2985 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2988 slist_free_strings_full(ref_id_list);
2990 new_ref_str = new_ref->str;
2991 g_string_free(new_ref, FALSE);
2996 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2997 const gchar *fmt, const gchar *qmark,
2998 const gchar *body, gboolean rewrap,
2999 gboolean need_unescape,
3000 const gchar *err_msg)
3002 MsgInfo* dummyinfo = NULL;
3003 gchar *quote_str = NULL;
3005 gboolean prev_autowrap;
3006 const gchar *trimmed_body = body;
3007 gint cursor_pos = -1;
3008 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3009 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3014 SIGNAL_BLOCK(buffer);
3017 dummyinfo = compose_msginfo_new_from_compose(compose);
3018 msginfo = dummyinfo;
3021 if (qmark != NULL) {
3023 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3024 compose->gtkaspell);
3026 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3028 quote_fmt_scan_string(qmark);
3031 buf = quote_fmt_get_buffer();
3033 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3035 Xstrdup_a(quote_str, buf, goto error)
3038 if (fmt && *fmt != '\0') {
3041 while (*trimmed_body == '\n')
3045 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3046 compose->gtkaspell);
3048 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3050 if (need_unescape) {
3053 /* decode \-escape sequences in the internal representation of the quote format */
3054 tmp = g_malloc(strlen(fmt)+1);
3055 pref_get_unescaped_pref(tmp, fmt);
3056 quote_fmt_scan_string(tmp);
3060 quote_fmt_scan_string(fmt);
3064 buf = quote_fmt_get_buffer();
3066 gint line = quote_fmt_get_line();
3067 alertpanel_error(err_msg, line);
3073 prev_autowrap = compose->autowrap;
3074 compose->autowrap = FALSE;
3076 mark = gtk_text_buffer_get_insert(buffer);
3077 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3078 if (g_utf8_validate(buf, -1, NULL)) {
3079 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3081 gchar *tmpout = NULL;
3082 tmpout = conv_codeset_strdup
3083 (buf, conv_get_locale_charset_str_no_utf8(),
3085 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3087 tmpout = g_malloc(strlen(buf)*2+1);
3088 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3090 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3094 cursor_pos = quote_fmt_get_cursor_pos();
3095 if (cursor_pos == -1)
3096 cursor_pos = gtk_text_iter_get_offset(&iter);
3097 compose->set_cursor_pos = cursor_pos;
3099 gtk_text_buffer_get_start_iter(buffer, &iter);
3100 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3101 gtk_text_buffer_place_cursor(buffer, &iter);
3103 compose->autowrap = prev_autowrap;
3104 if (compose->autowrap && rewrap)
3105 compose_wrap_all(compose);
3112 SIGNAL_UNBLOCK(buffer);
3114 procmsg_msginfo_free( dummyinfo );
3119 /* if ml_post is of type addr@host and from is of type
3120 * addr-anything@host, return TRUE
3122 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3124 gchar *left_ml = NULL;
3125 gchar *right_ml = NULL;
3126 gchar *left_from = NULL;
3127 gchar *right_from = NULL;
3128 gboolean result = FALSE;
3130 if (!ml_post || !from)
3133 left_ml = g_strdup(ml_post);
3134 if (strstr(left_ml, "@")) {
3135 right_ml = strstr(left_ml, "@")+1;
3136 *(strstr(left_ml, "@")) = '\0';
3139 left_from = g_strdup(from);
3140 if (strstr(left_from, "@")) {
3141 right_from = strstr(left_from, "@")+1;
3142 *(strstr(left_from, "@")) = '\0';
3145 if (left_ml && left_from && right_ml && right_from
3146 && !strncmp(left_from, left_ml, strlen(left_ml))
3147 && !strcmp(right_from, right_ml)) {
3156 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3157 gboolean respect_default_to)
3161 if (!folder || !folder->prefs)
3164 if (respect_default_to && folder->prefs->enable_default_to) {
3165 compose_entry_append(compose, folder->prefs->default_to,
3166 COMPOSE_TO, PREF_FOLDER);
3167 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3169 if (folder->prefs->enable_default_cc)
3170 compose_entry_append(compose, folder->prefs->default_cc,
3171 COMPOSE_CC, PREF_FOLDER);
3172 if (folder->prefs->enable_default_bcc)
3173 compose_entry_append(compose, folder->prefs->default_bcc,
3174 COMPOSE_BCC, PREF_FOLDER);
3175 if (folder->prefs->enable_default_replyto)
3176 compose_entry_append(compose, folder->prefs->default_replyto,
3177 COMPOSE_REPLYTO, PREF_FOLDER);
3180 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3185 if (!compose || !msginfo)
3188 if (msginfo->subject && *msginfo->subject) {
3189 buf = p = g_strdup(msginfo->subject);
3190 p += subject_get_prefix_length(p);
3191 memmove(buf, p, strlen(p) + 1);
3193 buf2 = g_strdup_printf("Re: %s", buf);
3194 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3199 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3202 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3203 gboolean to_all, gboolean to_ml,
3205 gboolean followup_and_reply_to)
3207 GSList *cc_list = NULL;
3210 gchar *replyto = NULL;
3211 gchar *ac_email = NULL;
3213 gboolean reply_to_ml = FALSE;
3214 gboolean default_reply_to = FALSE;
3216 cm_return_if_fail(compose->account != NULL);
3217 cm_return_if_fail(msginfo != NULL);
3219 reply_to_ml = to_ml && compose->ml_post;
3221 default_reply_to = msginfo->folder &&
3222 msginfo->folder->prefs->enable_default_reply_to;
3224 if (compose->account->protocol != A_NNTP) {
3225 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3227 if (reply_to_ml && !default_reply_to) {
3229 gboolean is_subscr = is_subscription(compose->ml_post,
3232 /* normal answer to ml post with a reply-to */
3233 compose_entry_append(compose,
3235 COMPOSE_TO, PREF_ML);
3236 if (compose->replyto)
3237 compose_entry_append(compose,
3239 COMPOSE_CC, PREF_ML);
3241 /* answer to subscription confirmation */
3242 if (compose->replyto)
3243 compose_entry_append(compose,
3245 COMPOSE_TO, PREF_ML);
3246 else if (msginfo->from)
3247 compose_entry_append(compose,
3249 COMPOSE_TO, PREF_ML);
3252 else if (!(to_all || to_sender) && default_reply_to) {
3253 compose_entry_append(compose,
3254 msginfo->folder->prefs->default_reply_to,
3255 COMPOSE_TO, PREF_FOLDER);
3256 compose_entry_mark_default_to(compose,
3257 msginfo->folder->prefs->default_reply_to);
3262 Xstrdup_a(tmp1, msginfo->from, return);
3263 extract_address(tmp1);
3264 if (to_all || to_sender ||
3265 !account_find_from_address(tmp1, FALSE))
3266 compose_entry_append(compose,
3267 (compose->replyto && !to_sender)
3268 ? compose->replyto :
3269 msginfo->from ? msginfo->from : "",
3270 COMPOSE_TO, PREF_NONE);
3271 else if (!to_all && !to_sender) {
3272 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3273 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3274 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3275 if (compose->replyto) {
3276 compose_entry_append(compose,
3278 COMPOSE_TO, PREF_NONE);
3280 compose_entry_append(compose,
3281 msginfo->from ? msginfo->from : "",
3282 COMPOSE_TO, PREF_NONE);
3285 /* replying to own mail, use original recp */
3286 compose_entry_append(compose,
3287 msginfo->to ? msginfo->to : "",
3288 COMPOSE_TO, PREF_NONE);
3289 compose_entry_append(compose,
3290 msginfo->cc ? msginfo->cc : "",
3291 COMPOSE_CC, PREF_NONE);
3296 if (to_sender || (compose->followup_to &&
3297 !strncmp(compose->followup_to, "poster", 6)))
3298 compose_entry_append
3300 (compose->replyto ? compose->replyto :
3301 msginfo->from ? msginfo->from : ""),
3302 COMPOSE_TO, PREF_NONE);
3304 else if (followup_and_reply_to || to_all) {
3305 compose_entry_append
3307 (compose->replyto ? compose->replyto :
3308 msginfo->from ? msginfo->from : ""),
3309 COMPOSE_TO, PREF_NONE);
3311 compose_entry_append
3313 compose->followup_to ? compose->followup_to :
3314 compose->newsgroups ? compose->newsgroups : "",
3315 COMPOSE_NEWSGROUPS, PREF_NONE);
3318 compose_entry_append
3320 compose->followup_to ? compose->followup_to :
3321 compose->newsgroups ? compose->newsgroups : "",
3322 COMPOSE_NEWSGROUPS, PREF_NONE);
3324 compose_reply_set_subject(compose, msginfo);
3326 if (to_ml && compose->ml_post) return;
3327 if (!to_all || compose->account->protocol == A_NNTP) return;
3329 if (compose->replyto) {
3330 Xstrdup_a(replyto, compose->replyto, return);
3331 extract_address(replyto);
3333 if (msginfo->from) {
3334 Xstrdup_a(from, msginfo->from, return);
3335 extract_address(from);
3338 if (replyto && from)
3339 cc_list = address_list_append_with_comments(cc_list, from);
3340 if (to_all && msginfo->folder &&
3341 msginfo->folder->prefs->enable_default_reply_to)
3342 cc_list = address_list_append_with_comments(cc_list,
3343 msginfo->folder->prefs->default_reply_to);
3344 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3345 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3347 ac_email = g_utf8_strdown(compose->account->address, -1);
3350 for (cur = cc_list; cur != NULL; cur = cur->next) {
3351 gchar *addr = g_utf8_strdown(cur->data, -1);
3352 extract_address(addr);
3354 if (strcmp(ac_email, addr))
3355 compose_entry_append(compose, (gchar *)cur->data,
3356 COMPOSE_CC, PREF_NONE);
3358 debug_print("Cc address same as compose account's, ignoring\n");
3363 slist_free_strings_full(cc_list);
3369 #define SET_ENTRY(entry, str) \
3372 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3375 #define SET_ADDRESS(type, str) \
3378 compose_entry_append(compose, str, type, PREF_NONE); \
3381 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3383 cm_return_if_fail(msginfo != NULL);
3385 SET_ENTRY(subject_entry, msginfo->subject);
3386 SET_ENTRY(from_name, msginfo->from);
3387 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3388 SET_ADDRESS(COMPOSE_CC, compose->cc);
3389 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3390 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3391 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3392 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3394 compose_update_priority_menu_item(compose);
3395 compose_update_privacy_system_menu_item(compose, FALSE);
3396 compose_show_first_last_header(compose, TRUE);
3402 static void compose_insert_sig(Compose *compose, gboolean replace)
3404 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3405 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3407 GtkTextIter iter, iter_end;
3408 gint cur_pos, ins_pos;
3409 gboolean prev_autowrap;
3410 gboolean found = FALSE;
3411 gboolean exists = FALSE;
3413 cm_return_if_fail(compose->account != NULL);
3417 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3418 G_CALLBACK(compose_changed_cb),
3421 mark = gtk_text_buffer_get_insert(buffer);
3422 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3423 cur_pos = gtk_text_iter_get_offset (&iter);
3426 gtk_text_buffer_get_end_iter(buffer, &iter);
3428 exists = (compose->sig_str != NULL);
3431 GtkTextIter first_iter, start_iter, end_iter;
3433 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3435 if (!exists || compose->sig_str[0] == '\0')
3438 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3439 compose->signature_tag);
3442 /* include previous \n\n */
3443 gtk_text_iter_backward_chars(&first_iter, 1);
3444 start_iter = first_iter;
3445 end_iter = first_iter;
3447 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3448 compose->signature_tag);
3449 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3450 compose->signature_tag);
3452 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3458 g_free(compose->sig_str);
3459 compose->sig_str = account_get_signature_str(compose->account);
3461 cur_pos = gtk_text_iter_get_offset(&iter);
3463 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3464 g_free(compose->sig_str);
3465 compose->sig_str = NULL;
3467 if (compose->sig_inserted == FALSE)
3468 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3469 compose->sig_inserted = TRUE;
3471 cur_pos = gtk_text_iter_get_offset(&iter);
3472 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3474 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3475 gtk_text_iter_forward_chars(&iter, 1);
3476 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3477 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3479 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3480 cur_pos = gtk_text_buffer_get_char_count (buffer);
3483 /* put the cursor where it should be
3484 * either where the quote_fmt says, either where it was */
3485 if (compose->set_cursor_pos < 0)
3486 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3488 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3489 compose->set_cursor_pos);
3491 compose->set_cursor_pos = -1;
3492 gtk_text_buffer_place_cursor(buffer, &iter);
3493 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3494 G_CALLBACK(compose_changed_cb),
3500 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3503 GtkTextBuffer *buffer;
3506 const gchar *cur_encoding;
3507 gchar buf[BUFFSIZE];
3510 gboolean prev_autowrap;
3511 gboolean badtxt = FALSE;
3512 struct stat file_stat;
3514 GString *file_contents = NULL;
3516 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3518 /* get the size of the file we are about to insert */
3519 ret = g_stat(file, &file_stat);
3521 gchar *shortfile = g_path_get_basename(file);
3522 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3524 return COMPOSE_INSERT_NO_FILE;
3525 } else if (prefs_common.warn_large_insert == TRUE) {
3527 /* ask user for confirmation if the file is large */
3528 if (prefs_common.warn_large_insert_size < 0 ||
3529 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3533 msg = g_strdup_printf(_("You are about to insert a file of %s "
3534 "in the message body. Are you sure you want to do that?"),
3535 to_human_readable(file_stat.st_size));
3536 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3537 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3540 /* do we ask for confirmation next time? */
3541 if (aval & G_ALERTDISABLE) {
3542 /* no confirmation next time, disable feature in preferences */
3543 aval &= ~G_ALERTDISABLE;
3544 prefs_common.warn_large_insert = FALSE;
3547 /* abort file insertion if user canceled action */
3548 if (aval != G_ALERTALTERNATE) {
3549 return COMPOSE_INSERT_NO_FILE;
3555 if ((fp = g_fopen(file, "rb")) == NULL) {
3556 FILE_OP_ERROR(file, "fopen");
3557 return COMPOSE_INSERT_READ_ERROR;
3560 prev_autowrap = compose->autowrap;
3561 compose->autowrap = FALSE;
3563 text = GTK_TEXT_VIEW(compose->text);
3564 buffer = gtk_text_view_get_buffer(text);
3565 mark = gtk_text_buffer_get_insert(buffer);
3566 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3568 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3569 G_CALLBACK(text_inserted),
3572 cur_encoding = conv_get_locale_charset_str_no_utf8();
3574 file_contents = g_string_new("");
3575 while (fgets(buf, sizeof(buf), fp) != NULL) {
3578 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3579 str = g_strdup(buf);
3581 str = conv_codeset_strdup
3582 (buf, cur_encoding, CS_INTERNAL);
3585 /* strip <CR> if DOS/Windows file,
3586 replace <CR> with <LF> if Macintosh file. */
3589 if (len > 0 && str[len - 1] != '\n') {
3591 if (str[len] == '\r') str[len] = '\n';
3594 file_contents = g_string_append(file_contents, str);
3598 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3599 g_string_free(file_contents, TRUE);
3601 compose_changed_cb(NULL, compose);
3602 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3603 G_CALLBACK(text_inserted),
3605 compose->autowrap = prev_autowrap;
3606 if (compose->autowrap)
3607 compose_wrap_all(compose);
3612 return COMPOSE_INSERT_INVALID_CHARACTER;
3614 return COMPOSE_INSERT_SUCCESS;
3617 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3618 const gchar *filename,
3619 const gchar *content_type,
3620 const gchar *charset)
3628 GtkListStore *store;
3630 gboolean has_binary = FALSE;
3632 if (!is_file_exist(file)) {
3633 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3634 gboolean result = FALSE;
3635 if (file_from_uri && is_file_exist(file_from_uri)) {
3636 result = compose_attach_append(
3637 compose, file_from_uri,
3638 filename, content_type,
3641 g_free(file_from_uri);
3644 alertpanel_error("File %s doesn't exist\n", filename);
3647 if ((size = get_file_size(file)) < 0) {
3648 alertpanel_error("Can't get file size of %s\n", filename);
3652 alertpanel_error(_("File %s is empty."), filename);
3655 if ((fp = g_fopen(file, "rb")) == NULL) {
3656 alertpanel_error(_("Can't read %s."), filename);
3661 ainfo = g_new0(AttachInfo, 1);
3662 auto_ainfo = g_auto_pointer_new_with_free
3663 (ainfo, (GFreeFunc) compose_attach_info_free);
3664 ainfo->file = g_strdup(file);
3667 ainfo->content_type = g_strdup(content_type);
3668 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3670 MsgFlags flags = {0, 0};
3672 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3673 ainfo->encoding = ENC_7BIT;
3675 ainfo->encoding = ENC_8BIT;
3677 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3678 if (msginfo && msginfo->subject)
3679 name = g_strdup(msginfo->subject);
3681 name = g_path_get_basename(filename ? filename : file);
3683 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3685 procmsg_msginfo_free(msginfo);
3687 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3688 ainfo->charset = g_strdup(charset);
3689 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3691 ainfo->encoding = ENC_BASE64;
3693 name = g_path_get_basename(filename ? filename : file);
3694 ainfo->name = g_strdup(name);
3698 ainfo->content_type = procmime_get_mime_type(file);
3699 if (!ainfo->content_type) {
3700 ainfo->content_type =
3701 g_strdup("application/octet-stream");
3702 ainfo->encoding = ENC_BASE64;
3703 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3705 procmime_get_encoding_for_text_file(file, &has_binary);
3707 ainfo->encoding = ENC_BASE64;
3708 name = g_path_get_basename(filename ? filename : file);
3709 ainfo->name = g_strdup(name);
3713 if (ainfo->name != NULL
3714 && !strcmp(ainfo->name, ".")) {
3715 g_free(ainfo->name);
3719 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3720 g_free(ainfo->content_type);
3721 ainfo->content_type = g_strdup("application/octet-stream");
3722 g_free(ainfo->charset);
3723 ainfo->charset = NULL;
3726 ainfo->size = (goffset)size;
3727 size_text = to_human_readable((goffset)size);
3729 store = GTK_LIST_STORE(gtk_tree_view_get_model
3730 (GTK_TREE_VIEW(compose->attach_clist)));
3732 gtk_list_store_append(store, &iter);
3733 gtk_list_store_set(store, &iter,
3734 COL_MIMETYPE, ainfo->content_type,
3735 COL_SIZE, size_text,
3736 COL_NAME, ainfo->name,
3737 COL_CHARSET, ainfo->charset,
3739 COL_AUTODATA, auto_ainfo,
3742 g_auto_pointer_free(auto_ainfo);
3743 compose_attach_update_label(compose);
3747 static void compose_use_signing(Compose *compose, gboolean use_signing)
3749 compose->use_signing = use_signing;
3750 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3753 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3755 compose->use_encryption = use_encryption;
3756 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3759 #define NEXT_PART_NOT_CHILD(info) \
3761 node = info->node; \
3762 while (node->children) \
3763 node = g_node_last_child(node); \
3764 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3767 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3771 MimeInfo *firsttext = NULL;
3772 MimeInfo *encrypted = NULL;
3775 const gchar *partname = NULL;
3777 mimeinfo = procmime_scan_message(msginfo);
3778 if (!mimeinfo) return;
3780 if (mimeinfo->node->children == NULL) {
3781 procmime_mimeinfo_free_all(mimeinfo);
3785 /* find first content part */
3786 child = (MimeInfo *) mimeinfo->node->children->data;
3787 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3788 child = (MimeInfo *)child->node->children->data;
3791 if (child->type == MIMETYPE_TEXT) {
3793 debug_print("First text part found\n");
3794 } else if (compose->mode == COMPOSE_REEDIT &&
3795 child->type == MIMETYPE_APPLICATION &&
3796 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3797 encrypted = (MimeInfo *)child->node->parent->data;
3800 child = (MimeInfo *) mimeinfo->node->children->data;
3801 while (child != NULL) {
3804 if (child == encrypted) {
3805 /* skip this part of tree */
3806 NEXT_PART_NOT_CHILD(child);
3810 if (child->type == MIMETYPE_MULTIPART) {
3811 /* get the actual content */
3812 child = procmime_mimeinfo_next(child);
3816 if (child == firsttext) {
3817 child = procmime_mimeinfo_next(child);
3821 outfile = procmime_get_tmp_file_name(child);
3822 if ((err = procmime_get_part(outfile, child)) < 0)
3823 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3825 gchar *content_type;
3827 content_type = procmime_get_content_type_str(child->type, child->subtype);
3829 /* if we meet a pgp signature, we don't attach it, but
3830 * we force signing. */
3831 if ((strcmp(content_type, "application/pgp-signature") &&
3832 strcmp(content_type, "application/pkcs7-signature") &&
3833 strcmp(content_type, "application/x-pkcs7-signature"))
3834 || compose->mode == COMPOSE_REDIRECT) {
3835 partname = procmime_mimeinfo_get_parameter(child, "filename");
3836 if (partname == NULL)
3837 partname = procmime_mimeinfo_get_parameter(child, "name");
3838 if (partname == NULL)
3840 compose_attach_append(compose, outfile,
3841 partname, content_type,
3842 procmime_mimeinfo_get_parameter(child, "charset"));
3844 compose_force_signing(compose, compose->account, NULL);
3846 g_free(content_type);
3849 NEXT_PART_NOT_CHILD(child);
3851 procmime_mimeinfo_free_all(mimeinfo);
3854 #undef NEXT_PART_NOT_CHILD
3859 WAIT_FOR_INDENT_CHAR,
3860 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3863 /* return indent length, we allow:
3864 indent characters followed by indent characters or spaces/tabs,
3865 alphabets and numbers immediately followed by indent characters,
3866 and the repeating sequences of the above
3867 If quote ends with multiple spaces, only the first one is included. */
3868 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3869 const GtkTextIter *start, gint *len)
3871 GtkTextIter iter = *start;
3875 IndentState state = WAIT_FOR_INDENT_CHAR;
3878 gint alnum_count = 0;
3879 gint space_count = 0;
3882 if (prefs_common.quote_chars == NULL) {
3886 while (!gtk_text_iter_ends_line(&iter)) {
3887 wc = gtk_text_iter_get_char(&iter);
3888 if (g_unichar_iswide(wc))
3890 clen = g_unichar_to_utf8(wc, ch);
3894 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3895 is_space = g_unichar_isspace(wc);
3897 if (state == WAIT_FOR_INDENT_CHAR) {
3898 if (!is_indent && !g_unichar_isalnum(wc))
3901 quote_len += alnum_count + space_count + 1;
3902 alnum_count = space_count = 0;
3903 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3906 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3907 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3911 else if (is_indent) {
3912 quote_len += alnum_count + space_count + 1;
3913 alnum_count = space_count = 0;
3916 state = WAIT_FOR_INDENT_CHAR;
3920 gtk_text_iter_forward_char(&iter);
3923 if (quote_len > 0 && space_count > 0)
3929 if (quote_len > 0) {
3931 gtk_text_iter_forward_chars(&iter, quote_len);
3932 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3938 /* return >0 if the line is itemized */
3939 static int compose_itemized_length(GtkTextBuffer *buffer,
3940 const GtkTextIter *start)
3942 GtkTextIter iter = *start;
3947 if (gtk_text_iter_ends_line(&iter))
3952 wc = gtk_text_iter_get_char(&iter);
3953 if (!g_unichar_isspace(wc))
3955 gtk_text_iter_forward_char(&iter);
3956 if (gtk_text_iter_ends_line(&iter))
3960 clen = g_unichar_to_utf8(wc, ch);
3964 if (!strchr("*-+", ch[0]))
3967 gtk_text_iter_forward_char(&iter);
3968 if (gtk_text_iter_ends_line(&iter))
3970 wc = gtk_text_iter_get_char(&iter);
3971 if (g_unichar_isspace(wc)) {
3977 /* return the string at the start of the itemization */
3978 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3979 const GtkTextIter *start)
3981 GtkTextIter iter = *start;
3984 GString *item_chars = g_string_new("");
3987 if (gtk_text_iter_ends_line(&iter))
3992 wc = gtk_text_iter_get_char(&iter);
3993 if (!g_unichar_isspace(wc))
3995 gtk_text_iter_forward_char(&iter);
3996 if (gtk_text_iter_ends_line(&iter))
3998 g_string_append_unichar(item_chars, wc);
4001 str = item_chars->str;
4002 g_string_free(item_chars, FALSE);
4006 /* return the number of spaces at a line's start */
4007 static int compose_left_offset_length(GtkTextBuffer *buffer,
4008 const GtkTextIter *start)
4010 GtkTextIter iter = *start;
4013 if (gtk_text_iter_ends_line(&iter))
4017 wc = gtk_text_iter_get_char(&iter);
4018 if (!g_unichar_isspace(wc))
4021 gtk_text_iter_forward_char(&iter);
4022 if (gtk_text_iter_ends_line(&iter))
4026 gtk_text_iter_forward_char(&iter);
4027 if (gtk_text_iter_ends_line(&iter))
4032 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4033 const GtkTextIter *start,
4034 GtkTextIter *break_pos,
4038 GtkTextIter iter = *start, line_end = *start;
4039 PangoLogAttr *attrs;
4046 gboolean can_break = FALSE;
4047 gboolean do_break = FALSE;
4048 gboolean was_white = FALSE;
4049 gboolean prev_dont_break = FALSE;
4051 gtk_text_iter_forward_to_line_end(&line_end);
4052 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4053 len = g_utf8_strlen(str, -1);
4057 g_warning("compose_get_line_break_pos: len = 0!\n");
4061 /* g_print("breaking line: %d: %s (len = %d)\n",
4062 gtk_text_iter_get_line(&iter), str, len); */
4064 attrs = g_new(PangoLogAttr, len + 1);
4066 pango_default_break(str, -1, NULL, attrs, len + 1);
4070 /* skip quote and leading spaces */
4071 for (i = 0; *p != '\0' && i < len; i++) {
4074 wc = g_utf8_get_char(p);
4075 if (i >= quote_len && !g_unichar_isspace(wc))
4077 if (g_unichar_iswide(wc))
4079 else if (*p == '\t')
4083 p = g_utf8_next_char(p);
4086 for (; *p != '\0' && i < len; i++) {
4087 PangoLogAttr *attr = attrs + i;
4091 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4094 was_white = attr->is_white;
4096 /* don't wrap URI */
4097 if ((uri_len = get_uri_len(p)) > 0) {
4099 if (pos > 0 && col > max_col) {
4109 wc = g_utf8_get_char(p);
4110 if (g_unichar_iswide(wc)) {
4112 if (prev_dont_break && can_break && attr->is_line_break)
4114 } else if (*p == '\t')
4118 if (pos > 0 && col > max_col) {
4123 if (*p == '-' || *p == '/')
4124 prev_dont_break = TRUE;
4126 prev_dont_break = FALSE;
4128 p = g_utf8_next_char(p);
4132 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4137 *break_pos = *start;
4138 gtk_text_iter_set_line_offset(break_pos, pos);
4143 static gboolean compose_join_next_line(Compose *compose,
4144 GtkTextBuffer *buffer,
4146 const gchar *quote_str)
4148 GtkTextIter iter_ = *iter, cur, prev, next, end;
4149 PangoLogAttr attrs[3];
4151 gchar *next_quote_str;
4154 gboolean keep_cursor = FALSE;
4156 if (!gtk_text_iter_forward_line(&iter_) ||
4157 gtk_text_iter_ends_line(&iter_)) {
4160 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4162 if ((quote_str || next_quote_str) &&
4163 strcmp2(quote_str, next_quote_str) != 0) {
4164 g_free(next_quote_str);
4167 g_free(next_quote_str);
4170 if (quote_len > 0) {
4171 gtk_text_iter_forward_chars(&end, quote_len);
4172 if (gtk_text_iter_ends_line(&end)) {
4177 /* don't join itemized lines */
4178 if (compose_itemized_length(buffer, &end) > 0) {
4182 /* don't join signature separator */
4183 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4186 /* delete quote str */
4188 gtk_text_buffer_delete(buffer, &iter_, &end);
4190 /* don't join line breaks put by the user */
4192 gtk_text_iter_backward_char(&cur);
4193 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4194 gtk_text_iter_forward_char(&cur);
4198 gtk_text_iter_forward_char(&cur);
4199 /* delete linebreak and extra spaces */
4200 while (gtk_text_iter_backward_char(&cur)) {
4201 wc1 = gtk_text_iter_get_char(&cur);
4202 if (!g_unichar_isspace(wc1))
4207 while (!gtk_text_iter_ends_line(&cur)) {
4208 wc1 = gtk_text_iter_get_char(&cur);
4209 if (!g_unichar_isspace(wc1))
4211 gtk_text_iter_forward_char(&cur);
4214 if (!gtk_text_iter_equal(&prev, &next)) {
4217 mark = gtk_text_buffer_get_insert(buffer);
4218 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4219 if (gtk_text_iter_equal(&prev, &cur))
4221 gtk_text_buffer_delete(buffer, &prev, &next);
4225 /* insert space if required */
4226 gtk_text_iter_backward_char(&prev);
4227 wc1 = gtk_text_iter_get_char(&prev);
4228 wc2 = gtk_text_iter_get_char(&next);
4229 gtk_text_iter_forward_char(&next);
4230 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4231 pango_default_break(str, -1, NULL, attrs, 3);
4232 if (!attrs[1].is_line_break ||
4233 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4234 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4236 gtk_text_iter_backward_char(&iter_);
4237 gtk_text_buffer_place_cursor(buffer, &iter_);
4246 #define ADD_TXT_POS(bp_, ep_, pti_) \
4247 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4248 last = last->next; \
4249 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4250 last->next = NULL; \
4252 g_warning("alloc error scanning URIs\n"); \
4255 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4257 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4258 GtkTextBuffer *buffer;
4259 GtkTextIter iter, break_pos, end_of_line;
4260 gchar *quote_str = NULL;
4262 gboolean wrap_quote = prefs_common.linewrap_quote;
4263 gboolean prev_autowrap = compose->autowrap;
4264 gint startq_offset = -1, noq_offset = -1;
4265 gint uri_start = -1, uri_stop = -1;
4266 gint nouri_start = -1, nouri_stop = -1;
4267 gint num_blocks = 0;
4268 gint quotelevel = -1;
4269 gboolean modified = force;
4270 gboolean removed = FALSE;
4271 gboolean modified_before_remove = FALSE;
4273 gboolean start = TRUE;
4274 gint itemized_len = 0, rem_item_len = 0;
4275 gchar *itemized_chars = NULL;
4276 gboolean item_continuation = FALSE;
4281 if (compose->draft_timeout_tag == -2) {
4285 compose->autowrap = FALSE;
4287 buffer = gtk_text_view_get_buffer(text);
4288 undo_wrapping(compose->undostruct, TRUE);
4293 mark = gtk_text_buffer_get_insert(buffer);
4294 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4298 if (compose->draft_timeout_tag == -2) {
4299 if (gtk_text_iter_ends_line(&iter)) {
4300 while (gtk_text_iter_ends_line(&iter) &&
4301 gtk_text_iter_forward_line(&iter))
4304 while (gtk_text_iter_backward_line(&iter)) {
4305 if (gtk_text_iter_ends_line(&iter)) {
4306 gtk_text_iter_forward_line(&iter);
4312 /* move to line start */
4313 gtk_text_iter_set_line_offset(&iter, 0);
4316 itemized_len = compose_itemized_length(buffer, &iter);
4318 if (!itemized_len) {
4319 itemized_len = compose_left_offset_length(buffer, &iter);
4320 item_continuation = TRUE;
4324 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4326 /* go until paragraph end (empty line) */
4327 while (start || !gtk_text_iter_ends_line(&iter)) {
4328 gchar *scanpos = NULL;
4329 /* parse table - in order of priority */
4331 const gchar *needle; /* token */
4333 /* token search function */
4334 gchar *(*search) (const gchar *haystack,
4335 const gchar *needle);
4336 /* part parsing function */
4337 gboolean (*parse) (const gchar *start,
4338 const gchar *scanpos,
4342 /* part to URI function */
4343 gchar *(*build_uri) (const gchar *bp,
4347 static struct table parser[] = {
4348 {"http://", strcasestr, get_uri_part, make_uri_string},
4349 {"https://", strcasestr, get_uri_part, make_uri_string},
4350 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4351 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4352 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4353 {"www.", strcasestr, get_uri_part, make_http_string},
4354 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4355 {"@", strcasestr, get_email_part, make_email_string}
4357 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4358 gint last_index = PARSE_ELEMS;
4360 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4364 if (!prev_autowrap && num_blocks == 0) {
4366 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4367 G_CALLBACK(text_inserted),
4370 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4373 uri_start = uri_stop = -1;
4375 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4378 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4379 if (startq_offset == -1)
4380 startq_offset = gtk_text_iter_get_offset(&iter);
4381 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4382 if (quotelevel > 2) {
4383 /* recycle colors */
4384 if (prefs_common.recycle_quote_colors)
4393 if (startq_offset == -1)
4394 noq_offset = gtk_text_iter_get_offset(&iter);
4398 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4401 if (gtk_text_iter_ends_line(&iter)) {
4403 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4404 prefs_common.linewrap_len,
4406 GtkTextIter prev, next, cur;
4407 if (prev_autowrap != FALSE || force) {
4408 compose->automatic_break = TRUE;
4410 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4411 compose->automatic_break = FALSE;
4412 if (itemized_len && compose->autoindent) {
4413 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4414 if (!item_continuation)
4415 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4417 } else if (quote_str && wrap_quote) {
4418 compose->automatic_break = TRUE;
4420 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4421 compose->automatic_break = FALSE;
4422 if (itemized_len && compose->autoindent) {
4423 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4424 if (!item_continuation)
4425 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4429 /* remove trailing spaces */
4431 rem_item_len = itemized_len;
4432 while (compose->autoindent && rem_item_len-- > 0)
4433 gtk_text_iter_backward_char(&cur);
4434 gtk_text_iter_backward_char(&cur);
4437 while (!gtk_text_iter_starts_line(&cur)) {
4440 gtk_text_iter_backward_char(&cur);
4441 wc = gtk_text_iter_get_char(&cur);
4442 if (!g_unichar_isspace(wc))
4446 if (!gtk_text_iter_equal(&prev, &next)) {
4447 gtk_text_buffer_delete(buffer, &prev, &next);
4449 gtk_text_iter_forward_char(&break_pos);
4453 gtk_text_buffer_insert(buffer, &break_pos,
4457 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4459 /* move iter to current line start */
4460 gtk_text_iter_set_line_offset(&iter, 0);
4467 /* move iter to next line start */
4473 if (!prev_autowrap && num_blocks > 0) {
4475 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4476 G_CALLBACK(text_inserted),
4480 while (!gtk_text_iter_ends_line(&end_of_line)) {
4481 gtk_text_iter_forward_char(&end_of_line);
4483 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4485 nouri_start = gtk_text_iter_get_offset(&iter);
4486 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4488 walk_pos = gtk_text_iter_get_offset(&iter);
4489 /* FIXME: this looks phony. scanning for anything in the parse table */
4490 for (n = 0; n < PARSE_ELEMS; n++) {
4493 tmp = parser[n].search(walk, parser[n].needle);
4495 if (scanpos == NULL || tmp < scanpos) {
4504 /* check if URI can be parsed */
4505 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4506 (const gchar **)&ep, FALSE)
4507 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4511 strlen(parser[last_index].needle);
4514 uri_start = walk_pos + (bp - o_walk);
4515 uri_stop = walk_pos + (ep - o_walk);
4519 gtk_text_iter_forward_line(&iter);
4522 if (startq_offset != -1) {
4523 GtkTextIter startquote, endquote;
4524 gtk_text_buffer_get_iter_at_offset(
4525 buffer, &startquote, startq_offset);
4528 switch (quotelevel) {
4530 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4531 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4532 gtk_text_buffer_apply_tag_by_name(
4533 buffer, "quote0", &startquote, &endquote);
4534 gtk_text_buffer_remove_tag_by_name(
4535 buffer, "quote1", &startquote, &endquote);
4536 gtk_text_buffer_remove_tag_by_name(
4537 buffer, "quote2", &startquote, &endquote);
4542 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4543 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4544 gtk_text_buffer_apply_tag_by_name(
4545 buffer, "quote1", &startquote, &endquote);
4546 gtk_text_buffer_remove_tag_by_name(
4547 buffer, "quote0", &startquote, &endquote);
4548 gtk_text_buffer_remove_tag_by_name(
4549 buffer, "quote2", &startquote, &endquote);
4554 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4555 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4556 gtk_text_buffer_apply_tag_by_name(
4557 buffer, "quote2", &startquote, &endquote);
4558 gtk_text_buffer_remove_tag_by_name(
4559 buffer, "quote0", &startquote, &endquote);
4560 gtk_text_buffer_remove_tag_by_name(
4561 buffer, "quote1", &startquote, &endquote);
4567 } else if (noq_offset != -1) {
4568 GtkTextIter startnoquote, endnoquote;
4569 gtk_text_buffer_get_iter_at_offset(
4570 buffer, &startnoquote, noq_offset);
4573 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4574 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4575 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4576 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4577 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4578 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4579 gtk_text_buffer_remove_tag_by_name(
4580 buffer, "quote0", &startnoquote, &endnoquote);
4581 gtk_text_buffer_remove_tag_by_name(
4582 buffer, "quote1", &startnoquote, &endnoquote);
4583 gtk_text_buffer_remove_tag_by_name(
4584 buffer, "quote2", &startnoquote, &endnoquote);
4590 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4591 GtkTextIter nouri_start_iter, nouri_end_iter;
4592 gtk_text_buffer_get_iter_at_offset(
4593 buffer, &nouri_start_iter, nouri_start);
4594 gtk_text_buffer_get_iter_at_offset(
4595 buffer, &nouri_end_iter, nouri_stop);
4596 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4597 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4598 gtk_text_buffer_remove_tag_by_name(
4599 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4600 modified_before_remove = modified;
4605 if (uri_start >= 0 && uri_stop > 0) {
4606 GtkTextIter uri_start_iter, uri_end_iter, back;
4607 gtk_text_buffer_get_iter_at_offset(
4608 buffer, &uri_start_iter, uri_start);
4609 gtk_text_buffer_get_iter_at_offset(
4610 buffer, &uri_end_iter, uri_stop);
4611 back = uri_end_iter;
4612 gtk_text_iter_backward_char(&back);
4613 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4614 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4615 gtk_text_buffer_apply_tag_by_name(
4616 buffer, "link", &uri_start_iter, &uri_end_iter);
4618 if (removed && !modified_before_remove) {
4624 // debug_print("not modified, out after %d lines\n", lines);
4628 // debug_print("modified, out after %d lines\n", lines);
4630 g_free(itemized_chars);
4633 undo_wrapping(compose->undostruct, FALSE);
4634 compose->autowrap = prev_autowrap;
4639 void compose_action_cb(void *data)
4641 Compose *compose = (Compose *)data;
4642 compose_wrap_all(compose);
4645 static void compose_wrap_all(Compose *compose)
4647 compose_wrap_all_full(compose, FALSE);
4650 static void compose_wrap_all_full(Compose *compose, gboolean force)
4652 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4653 GtkTextBuffer *buffer;
4655 gboolean modified = TRUE;
4657 buffer = gtk_text_view_get_buffer(text);
4659 gtk_text_buffer_get_start_iter(buffer, &iter);
4660 while (!gtk_text_iter_is_end(&iter) && modified)
4661 modified = compose_beautify_paragraph(compose, &iter, force);
4665 static void compose_set_title(Compose *compose)
4671 edited = compose->modified ? _(" [Edited]") : "";
4673 subject = gtk_editable_get_chars(
4674 GTK_EDITABLE(compose->subject_entry), 0, -1);
4676 #ifndef GENERIC_UMPC
4677 if (subject && strlen(subject))
4678 str = g_strdup_printf(_("%s - Compose message%s"),
4681 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4683 str = g_strdup(_("Compose message"));
4686 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4692 * compose_current_mail_account:
4694 * Find a current mail account (the currently selected account, or the
4695 * default account, if a news account is currently selected). If a
4696 * mail account cannot be found, display an error message.
4698 * Return value: Mail account, or NULL if not found.
4700 static PrefsAccount *
4701 compose_current_mail_account(void)
4705 if (cur_account && cur_account->protocol != A_NNTP)
4708 ac = account_get_default();
4709 if (!ac || ac->protocol == A_NNTP) {
4710 alertpanel_error(_("Account for sending mail is not specified.\n"
4711 "Please select a mail account before sending."));
4718 #define QUOTE_IF_REQUIRED(out, str) \
4720 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4724 len = strlen(str) + 3; \
4725 if ((__tmp = alloca(len)) == NULL) { \
4726 g_warning("can't allocate memory\n"); \
4727 g_string_free(header, TRUE); \
4730 g_snprintf(__tmp, len, "\"%s\"", str); \
4735 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4736 g_warning("can't allocate memory\n"); \
4737 g_string_free(header, TRUE); \
4740 strcpy(__tmp, str); \
4746 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4748 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4752 len = strlen(str) + 3; \
4753 if ((__tmp = alloca(len)) == NULL) { \
4754 g_warning("can't allocate memory\n"); \
4757 g_snprintf(__tmp, len, "\"%s\"", str); \
4762 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4763 g_warning("can't allocate memory\n"); \
4766 strcpy(__tmp, str); \
4772 static void compose_select_account(Compose *compose, PrefsAccount *account,
4775 gchar *from = NULL, *header = NULL;
4776 ComposeHeaderEntry *header_entry;
4777 #if GTK_CHECK_VERSION(2, 24, 0)
4781 cm_return_if_fail(account != NULL);
4783 compose->account = account;
4784 if (account->name && *account->name) {
4786 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4787 from = g_strdup_printf("%s <%s>",
4788 buf, account->address);
4789 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4791 from = g_strdup_printf("<%s>",
4793 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4798 compose_set_title(compose);
4800 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4801 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4803 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4804 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4805 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4807 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4809 activate_privacy_system(compose, account, FALSE);
4811 if (!init && compose->mode != COMPOSE_REDIRECT) {
4812 undo_block(compose->undostruct);
4813 compose_insert_sig(compose, TRUE);
4814 undo_unblock(compose->undostruct);
4817 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4818 #if !GTK_CHECK_VERSION(2, 24, 0)
4819 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4821 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4822 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4823 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4826 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4827 if (account->protocol == A_NNTP) {
4828 if (!strcmp(header, _("To:")))
4829 combobox_select_by_text(
4830 GTK_COMBO_BOX(header_entry->combo),
4833 if (!strcmp(header, _("Newsgroups:")))
4834 combobox_select_by_text(
4835 GTK_COMBO_BOX(header_entry->combo),
4843 /* use account's dict info if set */
4844 if (compose->gtkaspell) {
4845 if (account->enable_default_dictionary)
4846 gtkaspell_change_dict(compose->gtkaspell,
4847 account->default_dictionary, FALSE);
4848 if (account->enable_default_alt_dictionary)
4849 gtkaspell_change_alt_dict(compose->gtkaspell,
4850 account->default_alt_dictionary);
4851 if (account->enable_default_dictionary
4852 || account->enable_default_alt_dictionary)
4853 compose_spell_menu_changed(compose);
4858 gboolean compose_check_for_valid_recipient(Compose *compose) {
4859 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4860 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4861 gboolean recipient_found = FALSE;
4865 /* free to and newsgroup list */
4866 slist_free_strings_full(compose->to_list);
4867 compose->to_list = NULL;
4869 slist_free_strings_full(compose->newsgroup_list);
4870 compose->newsgroup_list = NULL;
4872 /* search header entries for to and newsgroup entries */
4873 for (list = compose->header_list; list; list = list->next) {
4876 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4877 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4880 if (entry[0] != '\0') {
4881 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4882 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4883 compose->to_list = address_list_append(compose->to_list, entry);
4884 recipient_found = TRUE;
4887 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4888 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4889 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4890 recipient_found = TRUE;
4897 return recipient_found;
4900 static gboolean compose_check_for_set_recipients(Compose *compose)
4902 if (compose->account->set_autocc && compose->account->auto_cc) {
4903 gboolean found_other = FALSE;
4905 /* search header entries for to and newsgroup entries */
4906 for (list = compose->header_list; list; list = list->next) {
4909 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4910 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4913 if (strcmp(entry, compose->account->auto_cc)
4914 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4924 if (compose->batch) {
4925 gtk_widget_show_all(compose->window);
4927 aval = alertpanel(_("Send"),
4928 _("The only recipient is the default CC address. Send anyway?"),
4929 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4930 if (aval != G_ALERTALTERNATE)
4934 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4935 gboolean found_other = FALSE;
4937 /* search header entries for to and newsgroup entries */
4938 for (list = compose->header_list; list; list = list->next) {
4941 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4942 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4945 if (strcmp(entry, compose->account->auto_bcc)
4946 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4956 if (compose->batch) {
4957 gtk_widget_show_all(compose->window);
4959 aval = alertpanel(_("Send"),
4960 _("The only recipient is the default BCC address. Send anyway?"),
4961 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4962 if (aval != G_ALERTALTERNATE)
4969 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4973 if (compose_check_for_valid_recipient(compose) == FALSE) {
4974 if (compose->batch) {
4975 gtk_widget_show_all(compose->window);
4977 alertpanel_error(_("Recipient is not specified."));
4981 if (compose_check_for_set_recipients(compose) == FALSE) {
4985 if (!compose->batch) {
4986 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4987 if (*str == '\0' && check_everything == TRUE &&
4988 compose->mode != COMPOSE_REDIRECT) {
4990 gchar *button_label;
4993 if (compose->sending)
4994 button_label = _("+_Send");
4996 button_label = _("+_Queue");
4997 message = g_strdup_printf(_("Subject is empty. %s"),
4998 compose->sending?_("Send it anyway?"):
4999 _("Queue it anyway?"));
5001 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
5002 GTK_STOCK_CANCEL, button_label, NULL);
5004 if (aval != G_ALERTALTERNATE)
5009 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5015 gint compose_send(Compose *compose)
5018 FolderItem *folder = NULL;
5020 gchar *msgpath = NULL;
5021 gboolean discard_window = FALSE;
5022 gchar *errstr = NULL;
5023 gchar *tmsgid = NULL;
5024 MainWindow *mainwin = mainwindow_get_mainwindow();
5025 gboolean queued_removed = FALSE;
5027 if (prefs_common.send_dialog_invisible
5028 || compose->batch == TRUE)
5029 discard_window = TRUE;
5031 compose_allow_user_actions (compose, FALSE);
5032 compose->sending = TRUE;
5034 if (compose_check_entries(compose, TRUE) == FALSE) {
5035 if (compose->batch) {
5036 gtk_widget_show_all(compose->window);
5042 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5045 if (compose->batch) {
5046 gtk_widget_show_all(compose->window);
5049 alertpanel_error(_("Could not queue message for sending:\n\n"
5050 "Charset conversion failed."));
5051 } else if (val == -5) {
5052 alertpanel_error(_("Could not queue message for sending:\n\n"
5053 "Couldn't get recipient encryption key."));
5054 } else if (val == -6) {
5056 } else if (val == -3) {
5057 if (privacy_peek_error())
5058 alertpanel_error(_("Could not queue message for sending:\n\n"
5059 "Signature failed: %s"), privacy_get_error());
5060 } else if (val == -2 && errno != 0) {
5061 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5063 alertpanel_error(_("Could not queue message for sending."));
5068 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5069 if (discard_window) {
5070 compose->sending = FALSE;
5071 compose_close(compose);
5072 /* No more compose access in the normal codepath
5073 * after this point! */
5078 alertpanel_error(_("The message was queued but could not be "
5079 "sent.\nUse \"Send queued messages\" from "
5080 "the main window to retry."));
5081 if (!discard_window) {
5088 if (msgpath == NULL) {
5089 msgpath = folder_item_fetch_msg(folder, msgnum);
5090 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5093 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5094 claws_unlink(msgpath);
5097 if (!discard_window) {
5099 if (!queued_removed)
5100 folder_item_remove_msg(folder, msgnum);
5101 folder_item_scan(folder);
5103 /* make sure we delete that */
5104 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5106 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5107 folder_item_remove_msg(folder, tmp->msgnum);
5108 procmsg_msginfo_free(tmp);
5115 if (!queued_removed)
5116 folder_item_remove_msg(folder, msgnum);
5117 folder_item_scan(folder);
5119 /* make sure we delete that */
5120 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5122 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5123 folder_item_remove_msg(folder, tmp->msgnum);
5124 procmsg_msginfo_free(tmp);
5127 if (!discard_window) {
5128 compose->sending = FALSE;
5129 compose_allow_user_actions (compose, TRUE);
5130 compose_close(compose);
5134 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5135 "the main window to retry."), errstr);
5138 alertpanel_error_log(_("The message was queued but could not be "
5139 "sent.\nUse \"Send queued messages\" from "
5140 "the main window to retry."));
5142 if (!discard_window) {
5151 toolbar_main_set_sensitive(mainwin);
5152 main_window_set_menu_sensitive(mainwin);
5158 compose_allow_user_actions (compose, TRUE);
5159 compose->sending = FALSE;
5160 compose->modified = TRUE;
5161 toolbar_main_set_sensitive(mainwin);
5162 main_window_set_menu_sensitive(mainwin);
5167 static gboolean compose_use_attach(Compose *compose)
5169 GtkTreeModel *model = gtk_tree_view_get_model
5170 (GTK_TREE_VIEW(compose->attach_clist));
5171 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5174 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5177 gchar buf[BUFFSIZE];
5179 gboolean first_to_address;
5180 gboolean first_cc_address;
5182 ComposeHeaderEntry *headerentry;
5183 const gchar *headerentryname;
5184 const gchar *cc_hdr;
5185 const gchar *to_hdr;
5186 gboolean err = FALSE;
5188 debug_print("Writing redirect header\n");
5190 cc_hdr = prefs_common_translated_header_name("Cc:");
5191 to_hdr = prefs_common_translated_header_name("To:");
5193 first_to_address = TRUE;
5194 for (list = compose->header_list; list; list = list->next) {
5195 headerentry = ((ComposeHeaderEntry *)list->data);
5196 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5198 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5199 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5200 Xstrdup_a(str, entstr, return -1);
5202 if (str[0] != '\0') {
5203 compose_convert_header
5204 (compose, buf, sizeof(buf), str,
5205 strlen("Resent-To") + 2, TRUE);
5207 if (first_to_address) {
5208 err |= (fprintf(fp, "Resent-To: ") < 0);
5209 first_to_address = FALSE;
5211 err |= (fprintf(fp, ",") < 0);
5213 err |= (fprintf(fp, "%s", buf) < 0);
5217 if (!first_to_address) {
5218 err |= (fprintf(fp, "\n") < 0);
5221 first_cc_address = TRUE;
5222 for (list = compose->header_list; list; list = list->next) {
5223 headerentry = ((ComposeHeaderEntry *)list->data);
5224 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5226 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5227 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5228 Xstrdup_a(str, strg, return -1);
5230 if (str[0] != '\0') {
5231 compose_convert_header
5232 (compose, buf, sizeof(buf), str,
5233 strlen("Resent-Cc") + 2, TRUE);
5235 if (first_cc_address) {
5236 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5237 first_cc_address = FALSE;
5239 err |= (fprintf(fp, ",") < 0);
5241 err |= (fprintf(fp, "%s", buf) < 0);
5245 if (!first_cc_address) {
5246 err |= (fprintf(fp, "\n") < 0);
5249 return (err ? -1:0);
5252 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5254 gchar buf[BUFFSIZE];
5256 const gchar *entstr;
5257 /* struct utsname utsbuf; */
5258 gboolean err = FALSE;
5260 cm_return_val_if_fail(fp != NULL, -1);
5261 cm_return_val_if_fail(compose->account != NULL, -1);
5262 cm_return_val_if_fail(compose->account->address != NULL, -1);
5265 get_rfc822_date(buf, sizeof(buf));
5266 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5269 if (compose->account->name && *compose->account->name) {
5270 compose_convert_header
5271 (compose, buf, sizeof(buf), compose->account->name,
5272 strlen("From: "), TRUE);
5273 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5274 buf, compose->account->address) < 0);
5276 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5279 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5280 if (*entstr != '\0') {
5281 Xstrdup_a(str, entstr, return -1);
5284 compose_convert_header(compose, buf, sizeof(buf), str,
5285 strlen("Subject: "), FALSE);
5286 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5290 /* Resent-Message-ID */
5291 if (compose->account->set_domain && compose->account->domain) {
5292 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5293 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5294 g_snprintf(buf, sizeof(buf), "%s",
5295 strchr(compose->account->address, '@') ?
5296 strchr(compose->account->address, '@')+1 :
5297 compose->account->address);
5299 g_snprintf(buf, sizeof(buf), "%s", "");
5302 if (compose->account->gen_msgid) {
5304 if (compose->account->msgid_with_addr) {
5305 addr = compose->account->address;
5307 generate_msgid(buf, sizeof(buf), addr);
5308 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5309 compose->msgid = g_strdup(buf);
5311 compose->msgid = NULL;
5314 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5317 /* separator between header and body */
5318 err |= (fputs("\n", fp) == EOF);
5320 return (err ? -1:0);
5323 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5327 gchar buf[BUFFSIZE];
5329 gboolean skip = FALSE;
5330 gboolean err = FALSE;
5331 gchar *not_included[]={
5332 "Return-Path:", "Delivered-To:", "Received:",
5333 "Subject:", "X-UIDL:", "AF:",
5334 "NF:", "PS:", "SRH:",
5335 "SFN:", "DSR:", "MID:",
5336 "CFG:", "PT:", "S:",
5337 "RQ:", "SSV:", "NSV:",
5338 "SSH:", "R:", "MAID:",
5339 "NAID:", "RMID:", "FMID:",
5340 "SCF:", "RRCPT:", "NG:",
5341 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5342 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5343 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5344 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5345 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5348 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5349 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5353 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5355 for (i = 0; not_included[i] != NULL; i++) {
5356 if (g_ascii_strncasecmp(buf, not_included[i],
5357 strlen(not_included[i])) == 0) {
5364 if (fputs(buf, fdest) == -1)
5367 if (!prefs_common.redirect_keep_from) {
5368 if (g_ascii_strncasecmp(buf, "From:",
5369 strlen("From:")) == 0) {
5370 err |= (fputs(" (by way of ", fdest) == EOF);
5371 if (compose->account->name
5372 && *compose->account->name) {
5373 compose_convert_header
5374 (compose, buf, sizeof(buf),
5375 compose->account->name,
5378 err |= (fprintf(fdest, "%s <%s>",
5380 compose->account->address) < 0);
5382 err |= (fprintf(fdest, "%s",
5383 compose->account->address) < 0);
5384 err |= (fputs(")", fdest) == EOF);
5388 if (fputs("\n", fdest) == -1)
5395 if (compose_redirect_write_headers(compose, fdest))
5398 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5399 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5412 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5414 GtkTextBuffer *buffer;
5415 GtkTextIter start, end;
5418 const gchar *out_codeset;
5419 EncodingType encoding = ENC_UNKNOWN;
5420 MimeInfo *mimemsg, *mimetext;
5422 const gchar *src_codeset = CS_INTERNAL;
5423 gchar *from_addr = NULL;
5424 gchar *from_name = NULL;
5426 if (action == COMPOSE_WRITE_FOR_SEND)
5427 attach_parts = TRUE;
5429 /* create message MimeInfo */
5430 mimemsg = procmime_mimeinfo_new();
5431 mimemsg->type = MIMETYPE_MESSAGE;
5432 mimemsg->subtype = g_strdup("rfc822");
5433 mimemsg->content = MIMECONTENT_MEM;
5434 mimemsg->tmp = TRUE; /* must free content later */
5435 mimemsg->data.mem = compose_get_header(compose);
5437 /* Create text part MimeInfo */
5438 /* get all composed text */
5439 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5440 gtk_text_buffer_get_start_iter(buffer, &start);
5441 gtk_text_buffer_get_end_iter(buffer, &end);
5442 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5444 out_codeset = conv_get_charset_str(compose->out_encoding);
5446 if (!out_codeset && is_ascii_str(chars)) {
5447 out_codeset = CS_US_ASCII;
5448 } else if (prefs_common.outgoing_fallback_to_ascii &&
5449 is_ascii_str(chars)) {
5450 out_codeset = CS_US_ASCII;
5451 encoding = ENC_7BIT;
5455 gchar *test_conv_global_out = NULL;
5456 gchar *test_conv_reply = NULL;
5458 /* automatic mode. be automatic. */
5459 codeconv_set_strict(TRUE);
5461 out_codeset = conv_get_outgoing_charset_str();
5463 debug_print("trying to convert to %s\n", out_codeset);
5464 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5467 if (!test_conv_global_out && compose->orig_charset
5468 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5469 out_codeset = compose->orig_charset;
5470 debug_print("failure; trying to convert to %s\n", out_codeset);
5471 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5474 if (!test_conv_global_out && !test_conv_reply) {
5476 out_codeset = CS_INTERNAL;
5477 debug_print("failure; finally using %s\n", out_codeset);
5479 g_free(test_conv_global_out);
5480 g_free(test_conv_reply);
5481 codeconv_set_strict(FALSE);
5484 if (encoding == ENC_UNKNOWN) {
5485 if (prefs_common.encoding_method == CTE_BASE64)
5486 encoding = ENC_BASE64;
5487 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5488 encoding = ENC_QUOTED_PRINTABLE;
5489 else if (prefs_common.encoding_method == CTE_8BIT)
5490 encoding = ENC_8BIT;
5492 encoding = procmime_get_encoding_for_charset(out_codeset);
5495 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5496 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5498 if (action == COMPOSE_WRITE_FOR_SEND) {
5499 codeconv_set_strict(TRUE);
5500 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5501 codeconv_set_strict(FALSE);
5507 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5508 "to the specified %s charset.\n"
5509 "Send it as %s?"), out_codeset, src_codeset);
5510 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5511 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5514 if (aval != G_ALERTALTERNATE) {
5519 out_codeset = src_codeset;
5525 out_codeset = src_codeset;
5530 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5531 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5532 strstr(buf, "\nFrom ") != NULL) {
5533 encoding = ENC_QUOTED_PRINTABLE;
5537 mimetext = procmime_mimeinfo_new();
5538 mimetext->content = MIMECONTENT_MEM;
5539 mimetext->tmp = TRUE; /* must free content later */
5540 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5541 * and free the data, which we need later. */
5542 mimetext->data.mem = g_strdup(buf);
5543 mimetext->type = MIMETYPE_TEXT;
5544 mimetext->subtype = g_strdup("plain");
5545 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5546 g_strdup(out_codeset));
5548 /* protect trailing spaces when signing message */
5549 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5550 privacy_system_can_sign(compose->privacy_system)) {
5551 encoding = ENC_QUOTED_PRINTABLE;
5554 debug_print("main text: %zd bytes encoded as %s in %d\n",
5555 strlen(buf), out_codeset, encoding);
5557 /* check for line length limit */
5558 if (action == COMPOSE_WRITE_FOR_SEND &&
5559 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5560 check_line_length(buf, 1000, &line) < 0) {
5564 msg = g_strdup_printf
5565 (_("Line %d exceeds the line length limit (998 bytes).\n"
5566 "The contents of the message might be broken on the way to the delivery.\n"
5568 "Send it anyway?"), line + 1);
5569 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5571 if (aval != G_ALERTALTERNATE) {
5577 if (encoding != ENC_UNKNOWN)
5578 procmime_encode_content(mimetext, encoding);
5580 /* append attachment parts */
5581 if (compose_use_attach(compose) && attach_parts) {
5582 MimeInfo *mimempart;
5583 gchar *boundary = NULL;
5584 mimempart = procmime_mimeinfo_new();
5585 mimempart->content = MIMECONTENT_EMPTY;
5586 mimempart->type = MIMETYPE_MULTIPART;
5587 mimempart->subtype = g_strdup("mixed");
5591 boundary = generate_mime_boundary(NULL);
5592 } while (strstr(buf, boundary) != NULL);
5594 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5597 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5599 g_node_append(mimempart->node, mimetext->node);
5600 g_node_append(mimemsg->node, mimempart->node);
5602 if (compose_add_attachments(compose, mimempart) < 0)
5605 g_node_append(mimemsg->node, mimetext->node);
5609 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5610 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5611 /* extract name and address */
5612 if (strstr(spec, " <") && strstr(spec, ">")) {
5613 from_addr = g_strdup(strrchr(spec, '<')+1);
5614 *(strrchr(from_addr, '>')) = '\0';
5615 from_name = g_strdup(spec);
5616 *(strrchr(from_name, '<')) = '\0';
5623 /* sign message if sending */
5624 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5625 privacy_system_can_sign(compose->privacy_system))
5626 if (!privacy_sign(compose->privacy_system, mimemsg,
5627 compose->account, from_addr)) {
5634 procmime_write_mimeinfo(mimemsg, fp);
5636 procmime_mimeinfo_free_all(mimemsg);
5641 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5643 GtkTextBuffer *buffer;
5644 GtkTextIter start, end;
5649 if ((fp = g_fopen(file, "wb")) == NULL) {
5650 FILE_OP_ERROR(file, "fopen");
5654 /* chmod for security */
5655 if (change_file_mode_rw(fp, file) < 0) {
5656 FILE_OP_ERROR(file, "chmod");
5657 g_warning("can't change file mode\n");
5660 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5661 gtk_text_buffer_get_start_iter(buffer, &start);
5662 gtk_text_buffer_get_end_iter(buffer, &end);
5663 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5665 chars = conv_codeset_strdup
5666 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5669 if (!chars) return -1;
5672 len = strlen(chars);
5673 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5674 FILE_OP_ERROR(file, "fwrite");
5683 if (fclose(fp) == EOF) {
5684 FILE_OP_ERROR(file, "fclose");
5691 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5694 MsgInfo *msginfo = compose->targetinfo;
5696 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5697 if (!msginfo) return -1;
5699 if (!force && MSG_IS_LOCKED(msginfo->flags))
5702 item = msginfo->folder;
5703 cm_return_val_if_fail(item != NULL, -1);
5705 if (procmsg_msg_exist(msginfo) &&
5706 (folder_has_parent_of_type(item, F_QUEUE) ||
5707 folder_has_parent_of_type(item, F_DRAFT)
5708 || msginfo == compose->autosaved_draft)) {
5709 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5710 g_warning("can't remove the old message\n");
5713 debug_print("removed reedit target %d\n", msginfo->msgnum);
5720 static void compose_remove_draft(Compose *compose)
5723 MsgInfo *msginfo = compose->targetinfo;
5724 drafts = account_get_special_folder(compose->account, F_DRAFT);
5726 if (procmsg_msg_exist(msginfo)) {
5727 folder_item_remove_msg(drafts, msginfo->msgnum);
5732 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5733 gboolean remove_reedit_target)
5735 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5738 static gboolean compose_warn_encryption(Compose *compose)
5740 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5741 AlertValue val = G_ALERTALTERNATE;
5743 if (warning == NULL)
5746 val = alertpanel_full(_("Encryption warning"), warning,
5747 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5748 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5749 if (val & G_ALERTDISABLE) {
5750 val &= ~G_ALERTDISABLE;
5751 if (val == G_ALERTALTERNATE)
5752 privacy_inhibit_encrypt_warning(compose->privacy_system,
5756 if (val == G_ALERTALTERNATE) {
5763 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5764 gchar **msgpath, gboolean check_subject,
5765 gboolean remove_reedit_target)
5772 PrefsAccount *mailac = NULL, *newsac = NULL;
5773 gboolean err = FALSE;
5775 debug_print("queueing message...\n");
5776 cm_return_val_if_fail(compose->account != NULL, -1);
5778 if (compose_check_entries(compose, check_subject) == FALSE) {
5779 if (compose->batch) {
5780 gtk_widget_show_all(compose->window);
5785 if (!compose->to_list && !compose->newsgroup_list) {
5786 g_warning("can't get recipient list.");
5790 if (compose->to_list) {
5791 if (compose->account->protocol != A_NNTP)
5792 mailac = compose->account;
5793 else if (cur_account && cur_account->protocol != A_NNTP)
5794 mailac = cur_account;
5795 else if (!(mailac = compose_current_mail_account())) {
5796 alertpanel_error(_("No account for sending mails available!"));
5801 if (compose->newsgroup_list) {
5802 if (compose->account->protocol == A_NNTP)
5803 newsac = compose->account;
5805 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5810 /* write queue header */
5811 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5812 G_DIR_SEPARATOR, compose, (guint) rand());
5813 debug_print("queuing to %s\n", tmp);
5814 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5815 FILE_OP_ERROR(tmp, "fopen");
5820 if (change_file_mode_rw(fp, tmp) < 0) {
5821 FILE_OP_ERROR(tmp, "chmod");
5822 g_warning("can't change file mode\n");
5825 /* queueing variables */
5826 err |= (fprintf(fp, "AF:\n") < 0);
5827 err |= (fprintf(fp, "NF:0\n") < 0);
5828 err |= (fprintf(fp, "PS:10\n") < 0);
5829 err |= (fprintf(fp, "SRH:1\n") < 0);
5830 err |= (fprintf(fp, "SFN:\n") < 0);
5831 err |= (fprintf(fp, "DSR:\n") < 0);
5833 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5835 err |= (fprintf(fp, "MID:\n") < 0);
5836 err |= (fprintf(fp, "CFG:\n") < 0);
5837 err |= (fprintf(fp, "PT:0\n") < 0);
5838 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5839 err |= (fprintf(fp, "RQ:\n") < 0);
5841 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5843 err |= (fprintf(fp, "SSV:\n") < 0);
5845 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5847 err |= (fprintf(fp, "NSV:\n") < 0);
5848 err |= (fprintf(fp, "SSH:\n") < 0);
5849 /* write recepient list */
5850 if (compose->to_list) {
5851 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5852 for (cur = compose->to_list->next; cur != NULL;
5854 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5855 err |= (fprintf(fp, "\n") < 0);
5857 /* write newsgroup list */
5858 if (compose->newsgroup_list) {
5859 err |= (fprintf(fp, "NG:") < 0);
5860 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5861 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5862 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5863 err |= (fprintf(fp, "\n") < 0);
5865 /* Sylpheed account IDs */
5867 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5869 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5872 if (compose->privacy_system != NULL) {
5873 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5874 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5875 if (compose->use_encryption) {
5877 if (!compose_warn_encryption(compose)) {
5883 if (mailac && mailac->encrypt_to_self) {
5884 GSList *tmp_list = g_slist_copy(compose->to_list);
5885 tmp_list = g_slist_append(tmp_list, compose->account->address);
5886 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5887 g_slist_free(tmp_list);
5889 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5891 if (encdata != NULL) {
5892 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5893 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5894 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5896 } /* else we finally dont want to encrypt */
5898 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5899 /* and if encdata was null, it means there's been a problem in
5902 g_warning("failed to write queue message");
5912 /* Save copy folder */
5913 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5914 gchar *savefolderid;
5916 savefolderid = compose_get_save_to(compose);
5917 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5918 g_free(savefolderid);
5920 /* Save copy folder */
5921 if (compose->return_receipt) {
5922 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5924 /* Message-ID of message replying to */
5925 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5928 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5929 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5932 /* Message-ID of message forwarding to */
5933 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5936 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5937 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5941 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5942 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5944 /* end of headers */
5945 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5947 if (compose->redirect_filename != NULL) {
5948 if (compose_redirect_write_to_file(compose, fp) < 0) {
5956 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5960 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5964 g_warning("failed to write queue message\n");
5970 if (fclose(fp) == EOF) {
5971 FILE_OP_ERROR(tmp, "fclose");
5977 if (item && *item) {
5980 queue = account_get_special_folder(compose->account, F_QUEUE);
5983 g_warning("can't find queue folder\n");
5988 folder_item_scan(queue);
5989 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5990 g_warning("can't queue the message\n");
5996 if (msgpath == NULL) {
6002 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
6003 compose_remove_reedit_target(compose, FALSE);
6006 if ((msgnum != NULL) && (item != NULL)) {
6014 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6017 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6019 struct stat statbuf;
6020 gchar *type, *subtype;
6021 GtkTreeModel *model;
6024 model = gtk_tree_view_get_model(tree_view);
6026 if (!gtk_tree_model_get_iter_first(model, &iter))
6029 gtk_tree_model_get(model, &iter,
6033 if (!is_file_exist(ainfo->file)) {
6034 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6035 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6036 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6038 if (val == G_ALERTDEFAULT) {
6043 mimepart = procmime_mimeinfo_new();
6044 mimepart->content = MIMECONTENT_FILE;
6045 mimepart->data.filename = g_strdup(ainfo->file);
6046 mimepart->tmp = FALSE; /* or we destroy our attachment */
6047 mimepart->offset = 0;
6049 g_stat(ainfo->file, &statbuf);
6050 mimepart->length = statbuf.st_size;
6052 type = g_strdup(ainfo->content_type);
6054 if (!strchr(type, '/')) {
6056 type = g_strdup("application/octet-stream");
6059 subtype = strchr(type, '/') + 1;
6060 *(subtype - 1) = '\0';
6061 mimepart->type = procmime_get_media_type(type);
6062 mimepart->subtype = g_strdup(subtype);
6065 if (mimepart->type == MIMETYPE_MESSAGE &&
6066 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6067 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6068 } else if (mimepart->type == MIMETYPE_TEXT) {
6069 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6070 /* Text parts with no name come from multipart/alternative
6071 * forwards. Make sure the recipient won't look at the
6072 * original HTML part by mistake. */
6073 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6074 ainfo->name = g_strdup_printf(_("Original %s part"),
6078 g_hash_table_insert(mimepart->typeparameters,
6079 g_strdup("charset"), g_strdup(ainfo->charset));
6081 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6082 if (mimepart->type == MIMETYPE_APPLICATION &&
6083 !strcmp2(mimepart->subtype, "octet-stream"))
6084 g_hash_table_insert(mimepart->typeparameters,
6085 g_strdup("name"), g_strdup(ainfo->name));
6086 g_hash_table_insert(mimepart->dispositionparameters,
6087 g_strdup("filename"), g_strdup(ainfo->name));
6088 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6091 if (mimepart->type == MIMETYPE_MESSAGE
6092 || mimepart->type == MIMETYPE_MULTIPART)
6093 ainfo->encoding = ENC_BINARY;
6094 else if (compose->use_signing) {
6095 if (ainfo->encoding == ENC_7BIT)
6096 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6097 else if (ainfo->encoding == ENC_8BIT)
6098 ainfo->encoding = ENC_BASE64;
6103 procmime_encode_content(mimepart, ainfo->encoding);
6105 g_node_append(parent->node, mimepart->node);
6106 } while (gtk_tree_model_iter_next(model, &iter));
6111 #define IS_IN_CUSTOM_HEADER(header) \
6112 (compose->account->add_customhdr && \
6113 custom_header_find(compose->account->customhdr_list, header) != NULL)
6115 static void compose_add_headerfield_from_headerlist(Compose *compose,
6117 const gchar *fieldname,
6118 const gchar *seperator)
6120 gchar *str, *fieldname_w_colon;
6121 gboolean add_field = FALSE;
6123 ComposeHeaderEntry *headerentry;
6124 const gchar *headerentryname;
6125 const gchar *trans_fieldname;
6128 if (IS_IN_CUSTOM_HEADER(fieldname))
6131 debug_print("Adding %s-fields\n", fieldname);
6133 fieldstr = g_string_sized_new(64);
6135 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6136 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6138 for (list = compose->header_list; list; list = list->next) {
6139 headerentry = ((ComposeHeaderEntry *)list->data);
6140 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6142 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6143 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6145 if (str[0] != '\0') {
6147 g_string_append(fieldstr, seperator);
6148 g_string_append(fieldstr, str);
6157 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6158 compose_convert_header
6159 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6160 strlen(fieldname) + 2, TRUE);
6161 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6165 g_free(fieldname_w_colon);
6166 g_string_free(fieldstr, TRUE);
6171 static gchar *compose_get_manual_headers_info(Compose *compose)
6173 GString *sh_header = g_string_new(" ");
6175 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6177 for (list = compose->header_list; list; list = list->next) {
6178 ComposeHeaderEntry *headerentry;
6181 gchar *headername_wcolon;
6182 const gchar *headername_trans;
6184 gboolean standard_header = FALSE;
6186 headerentry = ((ComposeHeaderEntry *)list->data);
6188 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6190 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6195 if (!strstr(tmp, ":")) {
6196 headername_wcolon = g_strconcat(tmp, ":", NULL);
6197 headername = g_strdup(tmp);
6199 headername_wcolon = g_strdup(tmp);
6200 headername = g_strdup(strtok(tmp, ":"));
6204 string = std_headers;
6205 while (*string != NULL) {
6206 headername_trans = prefs_common_translated_header_name(*string);
6207 if (!strcmp(headername_trans, headername_wcolon))
6208 standard_header = TRUE;
6211 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6212 g_string_append_printf(sh_header, "%s ", headername);
6214 g_free(headername_wcolon);
6216 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6217 return g_string_free(sh_header, FALSE);
6220 static gchar *compose_get_header(Compose *compose)
6222 gchar buf[BUFFSIZE];
6223 const gchar *entry_str;
6227 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6229 gchar *from_name = NULL, *from_address = NULL;
6232 cm_return_val_if_fail(compose->account != NULL, NULL);
6233 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6235 header = g_string_sized_new(64);
6238 get_rfc822_date(buf, sizeof(buf));
6239 g_string_append_printf(header, "Date: %s\n", buf);
6243 if (compose->account->name && *compose->account->name) {
6245 QUOTE_IF_REQUIRED(buf, compose->account->name);
6246 tmp = g_strdup_printf("%s <%s>",
6247 buf, compose->account->address);
6249 tmp = g_strdup_printf("%s",
6250 compose->account->address);
6252 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6253 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6255 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6256 from_address = g_strdup(compose->account->address);
6258 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6259 /* extract name and address */
6260 if (strstr(spec, " <") && strstr(spec, ">")) {
6261 from_address = g_strdup(strrchr(spec, '<')+1);
6262 *(strrchr(from_address, '>')) = '\0';
6263 from_name = g_strdup(spec);
6264 *(strrchr(from_name, '<')) = '\0';
6267 from_address = g_strdup(spec);
6274 if (from_name && *from_name) {
6275 compose_convert_header
6276 (compose, buf, sizeof(buf), from_name,
6277 strlen("From: "), TRUE);
6278 QUOTE_IF_REQUIRED(name, buf);
6280 g_string_append_printf(header, "From: %s <%s>\n",
6281 name, from_address);
6283 g_string_append_printf(header, "From: %s\n", from_address);
6286 g_free(from_address);
6289 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6292 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6295 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6299 * If this account is a NNTP account remove Bcc header from
6300 * message body since it otherwise will be publicly shown
6302 if (compose->account->protocol != A_NNTP)
6303 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6306 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6308 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6311 compose_convert_header(compose, buf, sizeof(buf), str,
6312 strlen("Subject: "), FALSE);
6313 g_string_append_printf(header, "Subject: %s\n", buf);
6319 if (compose->account->set_domain && compose->account->domain) {
6320 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6321 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6322 g_snprintf(buf, sizeof(buf), "%s",
6323 strchr(compose->account->address, '@') ?
6324 strchr(compose->account->address, '@')+1 :
6325 compose->account->address);
6327 g_snprintf(buf, sizeof(buf), "%s", "");
6330 if (compose->account->gen_msgid) {
6332 if (compose->account->msgid_with_addr) {
6333 addr = compose->account->address;
6335 generate_msgid(buf, sizeof(buf), addr);
6336 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6337 compose->msgid = g_strdup(buf);
6339 compose->msgid = NULL;
6342 if (compose->remove_references == FALSE) {
6344 if (compose->inreplyto && compose->to_list)
6345 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6348 if (compose->references)
6349 g_string_append_printf(header, "References: %s\n", compose->references);
6353 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6356 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6359 if (compose->account->organization &&
6360 strlen(compose->account->organization) &&
6361 !IS_IN_CUSTOM_HEADER("Organization")) {
6362 compose_convert_header(compose, buf, sizeof(buf),
6363 compose->account->organization,
6364 strlen("Organization: "), FALSE);
6365 g_string_append_printf(header, "Organization: %s\n", buf);
6368 /* Program version and system info */
6369 if (compose->account->gen_xmailer &&
6370 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6371 !compose->newsgroup_list) {
6372 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6374 gtk_major_version, gtk_minor_version, gtk_micro_version,
6377 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6378 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6380 gtk_major_version, gtk_minor_version, gtk_micro_version,
6384 /* custom headers */
6385 if (compose->account->add_customhdr) {
6388 for (cur = compose->account->customhdr_list; cur != NULL;
6390 CustomHeader *chdr = (CustomHeader *)cur->data;
6392 if (custom_header_is_allowed(chdr->name)
6393 && chdr->value != NULL
6394 && *(chdr->value) != '\0') {
6395 compose_convert_header
6396 (compose, buf, sizeof(buf),
6398 strlen(chdr->name) + 2, FALSE);
6399 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6404 /* Automatic Faces and X-Faces */
6405 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6406 g_string_append_printf(header, "X-Face: %s\n", buf);
6408 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6409 g_string_append_printf(header, "X-Face: %s\n", buf);
6411 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6412 g_string_append_printf(header, "Face: %s\n", buf);
6414 else if (get_default_face (buf, sizeof(buf)) == 0) {
6415 g_string_append_printf(header, "Face: %s\n", buf);
6419 switch (compose->priority) {
6420 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6421 "X-Priority: 1 (Highest)\n");
6423 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6424 "X-Priority: 2 (High)\n");
6426 case PRIORITY_NORMAL: break;
6427 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6428 "X-Priority: 4 (Low)\n");
6430 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6431 "X-Priority: 5 (Lowest)\n");
6433 default: debug_print("compose: priority unknown : %d\n",
6437 /* Request Return Receipt */
6438 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6439 if (compose->return_receipt) {
6440 if (compose->account->name
6441 && *compose->account->name) {
6442 compose_convert_header(compose, buf, sizeof(buf),
6443 compose->account->name,
6444 strlen("Disposition-Notification-To: "),
6446 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6448 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6452 /* get special headers */
6453 for (list = compose->header_list; list; list = list->next) {
6454 ComposeHeaderEntry *headerentry;
6457 gchar *headername_wcolon;
6458 const gchar *headername_trans;
6461 gboolean standard_header = FALSE;
6463 headerentry = ((ComposeHeaderEntry *)list->data);
6465 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6467 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6472 if (!strstr(tmp, ":")) {
6473 headername_wcolon = g_strconcat(tmp, ":", NULL);
6474 headername = g_strdup(tmp);
6476 headername_wcolon = g_strdup(tmp);
6477 headername = g_strdup(strtok(tmp, ":"));
6481 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6482 Xstrdup_a(headervalue, entry_str, return NULL);
6483 subst_char(headervalue, '\r', ' ');
6484 subst_char(headervalue, '\n', ' ');
6485 string = std_headers;
6486 while (*string != NULL) {
6487 headername_trans = prefs_common_translated_header_name(*string);
6488 if (!strcmp(headername_trans, headername_wcolon))
6489 standard_header = TRUE;
6492 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6493 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6496 g_free(headername_wcolon);
6500 g_string_free(header, FALSE);
6505 #undef IS_IN_CUSTOM_HEADER
6507 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6508 gint header_len, gboolean addr_field)
6510 gchar *tmpstr = NULL;
6511 const gchar *out_codeset = NULL;
6513 cm_return_if_fail(src != NULL);
6514 cm_return_if_fail(dest != NULL);
6516 if (len < 1) return;
6518 tmpstr = g_strdup(src);
6520 subst_char(tmpstr, '\n', ' ');
6521 subst_char(tmpstr, '\r', ' ');
6524 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6525 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6526 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6531 codeconv_set_strict(TRUE);
6532 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6533 conv_get_charset_str(compose->out_encoding));
6534 codeconv_set_strict(FALSE);
6536 if (!dest || *dest == '\0') {
6537 gchar *test_conv_global_out = NULL;
6538 gchar *test_conv_reply = NULL;
6540 /* automatic mode. be automatic. */
6541 codeconv_set_strict(TRUE);
6543 out_codeset = conv_get_outgoing_charset_str();
6545 debug_print("trying to convert to %s\n", out_codeset);
6546 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6549 if (!test_conv_global_out && compose->orig_charset
6550 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6551 out_codeset = compose->orig_charset;
6552 debug_print("failure; trying to convert to %s\n", out_codeset);
6553 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6556 if (!test_conv_global_out && !test_conv_reply) {
6558 out_codeset = CS_INTERNAL;
6559 debug_print("finally using %s\n", out_codeset);
6561 g_free(test_conv_global_out);
6562 g_free(test_conv_reply);
6563 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6565 codeconv_set_strict(FALSE);
6570 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6574 cm_return_if_fail(user_data != NULL);
6576 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6577 g_strstrip(address);
6578 if (*address != '\0') {
6579 gchar *name = procheader_get_fromname(address);
6580 extract_address(address);
6581 #ifndef USE_NEW_ADDRBOOK
6582 addressbook_add_contact(name, address, NULL, NULL);
6584 debug_print("%s: %s\n", name, address);
6585 if (addressadd_selection(name, address, NULL, NULL)) {
6586 debug_print( "addressbook_add_contact - added\n" );
6593 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6595 GtkWidget *menuitem;
6598 cm_return_if_fail(menu != NULL);
6599 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6601 menuitem = gtk_separator_menu_item_new();
6602 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6603 gtk_widget_show(menuitem);
6605 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6606 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6608 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6609 g_strstrip(address);
6610 if (*address == '\0') {
6611 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6614 g_signal_connect(G_OBJECT(menuitem), "activate",
6615 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6616 gtk_widget_show(menuitem);
6619 void compose_add_extra_header(gchar *header, GtkListStore *model)
6622 if (strcmp(header, "")) {
6623 COMBOBOX_ADD(model, header, COMPOSE_TO);
6627 void compose_add_extra_header_entries(GtkListStore *model)
6631 gchar buf[BUFFSIZE];
6634 if (extra_headers == NULL) {
6635 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6636 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6637 debug_print("extra headers file not found\n");
6638 goto extra_headers_done;
6640 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6641 lastc = strlen(buf) - 1; /* remove trailing control chars */
6642 while (lastc >= 0 && buf[lastc] != ':')
6643 buf[lastc--] = '\0';
6644 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6645 buf[lastc] = '\0'; /* remove trailing : for comparison */
6646 if (custom_header_is_allowed(buf)) {
6648 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6651 g_message("disallowed extra header line: %s\n", buf);
6655 g_message("invalid extra header line: %s\n", buf);
6661 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6662 extra_headers = g_slist_reverse(extra_headers);
6664 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6667 static void compose_create_header_entry(Compose *compose)
6669 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6676 const gchar *header = NULL;
6677 ComposeHeaderEntry *headerentry;
6678 gboolean standard_header = FALSE;
6679 GtkListStore *model;
6681 #if !(GTK_CHECK_VERSION(2,12,0))
6682 GtkTooltips *tips = compose->tooltips;
6685 headerentry = g_new0(ComposeHeaderEntry, 1);
6687 /* Combo box model */
6688 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6689 #if !GTK_CHECK_VERSION(2, 24, 0)
6690 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6692 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6694 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6696 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6698 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6699 COMPOSE_NEWSGROUPS);
6700 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6702 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6703 COMPOSE_FOLLOWUPTO);
6704 compose_add_extra_header_entries(model);
6707 #if GTK_CHECK_VERSION(2, 24, 0)
6708 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6709 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6710 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6711 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6712 gtk_combo_box_set_entry_text_column(combo, 0);
6714 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6715 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6716 G_CALLBACK(compose_grab_focus_cb), compose);
6717 gtk_widget_show(combo);
6720 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6721 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6724 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6725 compose->header_nextrow, compose->header_nextrow+1,
6726 GTK_SHRINK, GTK_FILL, 0, 0);
6727 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6728 const gchar *last_header_entry = gtk_entry_get_text(
6729 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6731 while (*string != NULL) {
6732 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6733 standard_header = TRUE;
6736 if (standard_header)
6737 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6739 if (!compose->header_last || !standard_header) {
6740 switch(compose->account->protocol) {
6742 header = prefs_common_translated_header_name("Newsgroups:");
6745 header = prefs_common_translated_header_name("To:");
6750 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6752 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6753 G_CALLBACK(compose_grab_focus_cb), compose);
6755 /* Entry field with cleanup button */
6756 button = gtk_button_new();
6757 gtk_button_set_image(GTK_BUTTON(button),
6758 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6759 gtk_widget_show(button);
6760 CLAWS_SET_TIP(button,
6761 _("Delete entry contents"));
6762 entry = gtk_entry_new();
6763 gtk_widget_show(entry);
6764 CLAWS_SET_TIP(entry,
6765 _("Use <tab> to autocomplete from addressbook"));
6766 hbox = gtk_hbox_new (FALSE, 0);
6767 gtk_widget_show(hbox);
6768 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6769 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6770 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6771 compose->header_nextrow, compose->header_nextrow+1,
6772 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6774 g_signal_connect(G_OBJECT(entry), "key-press-event",
6775 G_CALLBACK(compose_headerentry_key_press_event_cb),
6777 g_signal_connect(G_OBJECT(entry), "changed",
6778 G_CALLBACK(compose_headerentry_changed_cb),
6780 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6781 G_CALLBACK(compose_grab_focus_cb), compose);
6783 g_signal_connect(G_OBJECT(button), "clicked",
6784 G_CALLBACK(compose_headerentry_button_clicked_cb),
6788 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6789 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6790 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6791 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6792 G_CALLBACK(compose_header_drag_received_cb),
6794 g_signal_connect(G_OBJECT(entry), "drag-drop",
6795 G_CALLBACK(compose_drag_drop),
6797 g_signal_connect(G_OBJECT(entry), "populate-popup",
6798 G_CALLBACK(compose_entry_popup_extend),
6801 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6803 headerentry->compose = compose;
6804 headerentry->combo = combo;
6805 headerentry->entry = entry;
6806 headerentry->button = button;
6807 headerentry->hbox = hbox;
6808 headerentry->headernum = compose->header_nextrow;
6809 headerentry->type = PREF_NONE;
6811 compose->header_nextrow++;
6812 compose->header_last = headerentry;
6813 compose->header_list =
6814 g_slist_append(compose->header_list,
6818 static void compose_add_header_entry(Compose *compose, const gchar *header,
6819 gchar *text, ComposePrefType pref_type)
6821 ComposeHeaderEntry *last_header = compose->header_last;
6822 gchar *tmp = g_strdup(text), *email;
6823 gboolean replyto_hdr;
6825 replyto_hdr = (!strcasecmp(header,
6826 prefs_common_translated_header_name("Reply-To:")) ||
6828 prefs_common_translated_header_name("Followup-To:")) ||
6830 prefs_common_translated_header_name("In-Reply-To:")));
6832 extract_address(tmp);
6833 email = g_utf8_strdown(tmp, -1);
6835 if (replyto_hdr == FALSE &&
6836 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6838 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6839 header, text, (gint) pref_type);
6845 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6846 gtk_entry_set_text(GTK_ENTRY(
6847 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6849 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6850 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6851 last_header->type = pref_type;
6853 if (replyto_hdr == FALSE)
6854 g_hash_table_insert(compose->email_hashtable, email,
6855 GUINT_TO_POINTER(1));
6862 static void compose_destroy_headerentry(Compose *compose,
6863 ComposeHeaderEntry *headerentry)
6865 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6868 extract_address(text);
6869 email = g_utf8_strdown(text, -1);
6870 g_hash_table_remove(compose->email_hashtable, email);
6874 gtk_widget_destroy(headerentry->combo);
6875 gtk_widget_destroy(headerentry->entry);
6876 gtk_widget_destroy(headerentry->button);
6877 gtk_widget_destroy(headerentry->hbox);
6878 g_free(headerentry);
6881 static void compose_remove_header_entries(Compose *compose)
6884 for (list = compose->header_list; list; list = list->next)
6885 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6887 compose->header_last = NULL;
6888 g_slist_free(compose->header_list);
6889 compose->header_list = NULL;
6890 compose->header_nextrow = 1;
6891 compose_create_header_entry(compose);
6894 static GtkWidget *compose_create_header(Compose *compose)
6896 GtkWidget *from_optmenu_hbox;
6897 GtkWidget *header_scrolledwin_main;
6898 GtkWidget *header_table_main;
6899 GtkWidget *header_scrolledwin;
6900 GtkWidget *header_table;
6902 /* parent with account selection and from header */
6903 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6904 gtk_widget_show(header_scrolledwin_main);
6905 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6907 header_table_main = gtk_table_new(2, 2, FALSE);
6908 gtk_widget_show(header_table_main);
6909 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6910 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6911 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6913 from_optmenu_hbox = compose_account_option_menu_create(compose);
6914 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6915 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6917 /* child with header labels and entries */
6918 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6919 gtk_widget_show(header_scrolledwin);
6920 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6922 header_table = gtk_table_new(2, 2, FALSE);
6923 gtk_widget_show(header_table);
6924 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6925 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6926 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6928 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6929 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6931 compose->header_table = header_table;
6932 compose->header_list = NULL;
6933 compose->header_nextrow = 0;
6935 compose_create_header_entry(compose);
6937 compose->table = NULL;
6939 return header_scrolledwin_main;
6942 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6944 Compose *compose = (Compose *)data;
6945 GdkEventButton event;
6948 event.time = gtk_get_current_event_time();
6950 return attach_button_pressed(compose->attach_clist, &event, compose);
6953 static GtkWidget *compose_create_attach(Compose *compose)
6955 GtkWidget *attach_scrwin;
6956 GtkWidget *attach_clist;
6958 GtkListStore *store;
6959 GtkCellRenderer *renderer;
6960 GtkTreeViewColumn *column;
6961 GtkTreeSelection *selection;
6963 /* attachment list */
6964 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6965 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6966 GTK_POLICY_AUTOMATIC,
6967 GTK_POLICY_AUTOMATIC);
6968 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6970 store = gtk_list_store_new(N_ATTACH_COLS,
6976 G_TYPE_AUTO_POINTER,
6978 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6979 (GTK_TREE_MODEL(store)));
6980 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6981 g_object_unref(store);
6983 renderer = gtk_cell_renderer_text_new();
6984 column = gtk_tree_view_column_new_with_attributes
6985 (_("Mime type"), renderer, "text",
6986 COL_MIMETYPE, NULL);
6987 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6989 renderer = gtk_cell_renderer_text_new();
6990 column = gtk_tree_view_column_new_with_attributes
6991 (_("Size"), renderer, "text",
6993 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6995 renderer = gtk_cell_renderer_text_new();
6996 column = gtk_tree_view_column_new_with_attributes
6997 (_("Name"), renderer, "text",
6999 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
7001 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
7002 prefs_common.use_stripes_everywhere);
7003 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
7004 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
7006 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7007 G_CALLBACK(attach_selected), compose);
7008 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7009 G_CALLBACK(attach_button_pressed), compose);
7011 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7012 G_CALLBACK(popup_attach_button_pressed), compose);
7014 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
7015 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7016 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
7017 G_CALLBACK(popup_attach_button_pressed), compose);
7019 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7020 G_CALLBACK(attach_key_pressed), compose);
7023 gtk_drag_dest_set(attach_clist,
7024 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7025 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7026 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7027 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7028 G_CALLBACK(compose_attach_drag_received_cb),
7030 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7031 G_CALLBACK(compose_drag_drop),
7034 compose->attach_scrwin = attach_scrwin;
7035 compose->attach_clist = attach_clist;
7037 return attach_scrwin;
7040 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7041 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7043 static GtkWidget *compose_create_others(Compose *compose)
7046 GtkWidget *savemsg_checkbtn;
7047 GtkWidget *savemsg_combo;
7048 GtkWidget *savemsg_select;
7051 gchar *folderidentifier;
7053 /* Table for settings */
7054 table = gtk_table_new(3, 1, FALSE);
7055 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7056 gtk_widget_show(table);
7057 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7060 /* Save Message to folder */
7061 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7062 gtk_widget_show(savemsg_checkbtn);
7063 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7064 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7065 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7067 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7068 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7070 #if !GTK_CHECK_VERSION(2, 24, 0)
7071 savemsg_combo = gtk_combo_box_entry_new_text();
7073 savemsg_combo = gtk_combo_box_text_new_with_entry();
7075 compose->savemsg_checkbtn = savemsg_checkbtn;
7076 compose->savemsg_combo = savemsg_combo;
7077 gtk_widget_show(savemsg_combo);
7079 if (prefs_common.compose_save_to_history)
7080 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7081 prefs_common.compose_save_to_history);
7083 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7084 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7085 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7086 G_CALLBACK(compose_grab_focus_cb), compose);
7087 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7088 folderidentifier = folder_item_get_identifier(account_get_special_folder
7089 (compose->account, F_OUTBOX));
7090 compose_set_save_to(compose, folderidentifier);
7091 g_free(folderidentifier);
7094 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7095 gtk_widget_show(savemsg_select);
7096 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7097 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7098 G_CALLBACK(compose_savemsg_select_cb),
7104 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7106 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7107 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7110 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7115 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7118 path = folder_item_get_identifier(dest);
7120 compose_set_save_to(compose, path);
7124 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7125 GdkAtom clip, GtkTextIter *insert_place);
7128 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7132 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7134 if (event->button == 3) {
7136 GtkTextIter sel_start, sel_end;
7137 gboolean stuff_selected;
7139 /* move the cursor to allow GtkAspell to check the word
7140 * under the mouse */
7141 if (event->x && event->y) {
7142 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7143 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7145 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7148 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7149 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7152 stuff_selected = gtk_text_buffer_get_selection_bounds(
7154 &sel_start, &sel_end);
7156 gtk_text_buffer_place_cursor (buffer, &iter);
7157 /* reselect stuff */
7159 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7160 gtk_text_buffer_select_range(buffer,
7161 &sel_start, &sel_end);
7163 return FALSE; /* pass the event so that the right-click goes through */
7166 if (event->button == 2) {
7171 /* get the middle-click position to paste at the correct place */
7172 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7173 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7175 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7178 entry_paste_clipboard(compose, text,
7179 prefs_common.linewrap_pastes,
7180 GDK_SELECTION_PRIMARY, &iter);
7188 static void compose_spell_menu_changed(void *data)
7190 Compose *compose = (Compose *)data;
7192 GtkWidget *menuitem;
7193 GtkWidget *parent_item;
7194 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7197 if (compose->gtkaspell == NULL)
7200 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7201 "/Menu/Spelling/Options");
7203 /* setting the submenu removes /Spelling/Options from the factory
7204 * so we need to save it */
7206 if (parent_item == NULL) {
7207 parent_item = compose->aspell_options_menu;
7208 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7210 compose->aspell_options_menu = parent_item;
7212 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7214 spell_menu = g_slist_reverse(spell_menu);
7215 for (items = spell_menu;
7216 items; items = items->next) {
7217 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7218 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7219 gtk_widget_show(GTK_WIDGET(menuitem));
7221 g_slist_free(spell_menu);
7223 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7224 gtk_widget_show(parent_item);
7227 static void compose_dict_changed(void *data)
7229 Compose *compose = (Compose *) data;
7231 if(compose->gtkaspell &&
7232 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7235 gtkaspell_highlight_all(compose->gtkaspell);
7236 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7240 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7242 Compose *compose = (Compose *)data;
7243 GdkEventButton event;
7246 event.time = gtk_get_current_event_time();
7250 return text_clicked(compose->text, &event, compose);
7253 static gboolean compose_force_window_origin = TRUE;
7254 static Compose *compose_create(PrefsAccount *account,
7263 GtkWidget *handlebox;
7265 GtkWidget *notebook;
7267 GtkWidget *attach_hbox;
7268 GtkWidget *attach_lab1;
7269 GtkWidget *attach_lab2;
7274 GtkWidget *subject_hbox;
7275 GtkWidget *subject_frame;
7276 GtkWidget *subject_entry;
7280 GtkWidget *edit_vbox;
7281 GtkWidget *ruler_hbox;
7283 GtkWidget *scrolledwin;
7285 GtkTextBuffer *buffer;
7286 GtkClipboard *clipboard;
7288 UndoMain *undostruct;
7290 GtkWidget *popupmenu;
7291 GtkWidget *tmpl_menu;
7292 GtkActionGroup *action_group = NULL;
7295 GtkAspell * gtkaspell = NULL;
7298 static GdkGeometry geometry;
7300 cm_return_val_if_fail(account != NULL, NULL);
7302 debug_print("Creating compose window...\n");
7303 compose = g_new0(Compose, 1);
7305 compose->batch = batch;
7306 compose->account = account;
7307 compose->folder = folder;
7309 compose->mutex = cm_mutex_new();
7310 compose->set_cursor_pos = -1;
7312 #if !(GTK_CHECK_VERSION(2,12,0))
7313 compose->tooltips = tips;
7316 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7318 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7319 gtk_widget_set_size_request(window, prefs_common.compose_width,
7320 prefs_common.compose_height);
7322 if (!geometry.max_width) {
7323 geometry.max_width = gdk_screen_width();
7324 geometry.max_height = gdk_screen_height();
7327 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7328 &geometry, GDK_HINT_MAX_SIZE);
7329 if (!geometry.min_width) {
7330 geometry.min_width = 600;
7331 geometry.min_height = 440;
7333 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7334 &geometry, GDK_HINT_MIN_SIZE);
7336 #ifndef GENERIC_UMPC
7337 if (compose_force_window_origin)
7338 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7339 prefs_common.compose_y);
7341 g_signal_connect(G_OBJECT(window), "delete_event",
7342 G_CALLBACK(compose_delete_cb), compose);
7343 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7344 gtk_widget_realize(window);
7346 gtkut_widget_set_composer_icon(window);
7348 vbox = gtk_vbox_new(FALSE, 0);
7349 gtk_container_add(GTK_CONTAINER(window), vbox);
7351 compose->ui_manager = gtk_ui_manager_new();
7352 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7353 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7354 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7355 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7356 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7357 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7358 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7359 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7360 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7361 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7364 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7366 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7369 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7370 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7372 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7374 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7375 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7376 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7379 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7380 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7382 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7383 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7384 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7385 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7386 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7387 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7389 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7393 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7394 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7406 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7409 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7410 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7411 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7414 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7416 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7418 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7419 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7420 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7421 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7422 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7424 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7432 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7438 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7474 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7483 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)
7484 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)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7490 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)
7491 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)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7496 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)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7499 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7500 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)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7506 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)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7509 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7512 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)
7513 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)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7519 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7526 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)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7534 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7536 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7537 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7538 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7539 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7542 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7544 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7545 gtk_widget_show_all(menubar);
7547 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7549 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7551 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7554 if (prefs_common.toolbar_detachable) {
7555 handlebox = gtk_handle_box_new();
7557 handlebox = gtk_hbox_new(FALSE, 0);
7559 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7561 gtk_widget_realize(handlebox);
7563 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7566 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7570 vbox2 = gtk_vbox_new(FALSE, 2);
7571 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7572 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7575 notebook = gtk_notebook_new();
7576 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7577 gtk_widget_show(notebook);
7579 /* header labels and entries */
7580 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7581 compose_create_header(compose),
7582 gtk_label_new_with_mnemonic(_("Hea_der")));
7583 /* attachment list */
7584 attach_hbox = gtk_hbox_new(FALSE, 0);
7585 gtk_widget_show(attach_hbox);
7587 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7588 gtk_widget_show(attach_lab1);
7589 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7591 attach_lab2 = gtk_label_new("");
7592 gtk_widget_show(attach_lab2);
7593 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7595 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7596 compose_create_attach(compose),
7599 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7600 compose_create_others(compose),
7601 gtk_label_new_with_mnemonic(_("Othe_rs")));
7604 subject_hbox = gtk_hbox_new(FALSE, 0);
7605 gtk_widget_show(subject_hbox);
7607 subject_frame = gtk_frame_new(NULL);
7608 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7609 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7610 gtk_widget_show(subject_frame);
7612 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7613 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7614 gtk_widget_show(subject);
7616 label = gtk_label_new(_("Subject:"));
7617 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7618 gtk_widget_show(label);
7621 subject_entry = claws_spell_entry_new();
7623 subject_entry = gtk_entry_new();
7625 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7626 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7627 G_CALLBACK(compose_grab_focus_cb), compose);
7628 gtk_widget_show(subject_entry);
7629 compose->subject_entry = subject_entry;
7630 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7632 edit_vbox = gtk_vbox_new(FALSE, 0);
7634 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7637 ruler_hbox = gtk_hbox_new(FALSE, 0);
7638 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7640 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7641 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7642 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7646 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7647 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7648 GTK_POLICY_AUTOMATIC,
7649 GTK_POLICY_AUTOMATIC);
7650 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7652 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7654 text = gtk_text_view_new();
7655 if (prefs_common.show_compose_margin) {
7656 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7657 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7659 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7660 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7661 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7662 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7663 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7665 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7666 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7667 G_CALLBACK(compose_notebook_size_alloc), compose);
7668 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7669 G_CALLBACK(compose_edit_size_alloc),
7671 g_signal_connect(G_OBJECT(buffer), "changed",
7672 G_CALLBACK(compose_changed_cb), compose);
7673 g_signal_connect(G_OBJECT(text), "grab_focus",
7674 G_CALLBACK(compose_grab_focus_cb), compose);
7675 g_signal_connect(G_OBJECT(buffer), "insert_text",
7676 G_CALLBACK(text_inserted), compose);
7677 g_signal_connect(G_OBJECT(text), "button_press_event",
7678 G_CALLBACK(text_clicked), compose);
7680 g_signal_connect(G_OBJECT(text), "popup-menu",
7681 G_CALLBACK(compose_popup_menu), compose);
7683 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7684 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7685 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7686 G_CALLBACK(compose_popup_menu), compose);
7688 g_signal_connect(G_OBJECT(subject_entry), "changed",
7689 G_CALLBACK(compose_changed_cb), compose);
7690 g_signal_connect(G_OBJECT(subject_entry), "activate",
7691 G_CALLBACK(compose_subject_entry_activated), compose);
7694 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7695 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7696 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7697 g_signal_connect(G_OBJECT(text), "drag_data_received",
7698 G_CALLBACK(compose_insert_drag_received_cb),
7700 g_signal_connect(G_OBJECT(text), "drag-drop",
7701 G_CALLBACK(compose_drag_drop),
7703 g_signal_connect(G_OBJECT(text), "key-press-event",
7704 G_CALLBACK(completion_set_focus_to_subject),
7706 gtk_widget_show_all(vbox);
7708 /* pane between attach clist and text */
7709 paned = gtk_vpaned_new();
7710 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7712 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7713 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7715 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7717 gtk_paned_add1(GTK_PANED(paned), notebook);
7718 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7719 gtk_widget_show_all(paned);
7722 if (prefs_common.textfont) {
7723 PangoFontDescription *font_desc;
7725 font_desc = pango_font_description_from_string
7726 (prefs_common.textfont);
7728 gtk_widget_modify_font(text, font_desc);
7729 pango_font_description_free(font_desc);
7733 gtk_action_group_add_actions(action_group, compose_popup_entries,
7734 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7735 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7736 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7737 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7738 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7739 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7740 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7742 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7744 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7745 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7746 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7748 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7750 undostruct = undo_init(text);
7751 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7754 address_completion_start(window);
7756 compose->window = window;
7757 compose->vbox = vbox;
7758 compose->menubar = menubar;
7759 compose->handlebox = handlebox;
7761 compose->vbox2 = vbox2;
7763 compose->paned = paned;
7765 compose->attach_label = attach_lab2;
7767 compose->notebook = notebook;
7768 compose->edit_vbox = edit_vbox;
7769 compose->ruler_hbox = ruler_hbox;
7770 compose->ruler = ruler;
7771 compose->scrolledwin = scrolledwin;
7772 compose->text = text;
7774 compose->focused_editable = NULL;
7776 compose->popupmenu = popupmenu;
7778 compose->tmpl_menu = tmpl_menu;
7780 compose->mode = mode;
7781 compose->rmode = mode;
7783 compose->targetinfo = NULL;
7784 compose->replyinfo = NULL;
7785 compose->fwdinfo = NULL;
7787 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7788 g_str_equal, (GDestroyNotify) g_free, NULL);
7790 compose->replyto = NULL;
7792 compose->bcc = NULL;
7793 compose->followup_to = NULL;
7795 compose->ml_post = NULL;
7797 compose->inreplyto = NULL;
7798 compose->references = NULL;
7799 compose->msgid = NULL;
7800 compose->boundary = NULL;
7802 compose->autowrap = prefs_common.autowrap;
7803 compose->autoindent = prefs_common.auto_indent;
7804 compose->use_signing = FALSE;
7805 compose->use_encryption = FALSE;
7806 compose->privacy_system = NULL;
7808 compose->modified = FALSE;
7810 compose->return_receipt = FALSE;
7812 compose->to_list = NULL;
7813 compose->newsgroup_list = NULL;
7815 compose->undostruct = undostruct;
7817 compose->sig_str = NULL;
7819 compose->exteditor_file = NULL;
7820 compose->exteditor_pid = -1;
7821 compose->exteditor_tag = -1;
7822 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7825 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7826 if (mode != COMPOSE_REDIRECT) {
7827 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7828 strcmp(prefs_common.dictionary, "")) {
7829 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7830 prefs_common.alt_dictionary,
7831 conv_get_locale_charset_str(),
7832 prefs_common.misspelled_col,
7833 prefs_common.check_while_typing,
7834 prefs_common.recheck_when_changing_dict,
7835 prefs_common.use_alternate,
7836 prefs_common.use_both_dicts,
7837 GTK_TEXT_VIEW(text),
7838 GTK_WINDOW(compose->window),
7839 compose_dict_changed,
7840 compose_spell_menu_changed,
7843 alertpanel_error(_("Spell checker could not "
7845 gtkaspell_checkers_strerror());
7846 gtkaspell_checkers_reset_error();
7848 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7852 compose->gtkaspell = gtkaspell;
7853 compose_spell_menu_changed(compose);
7854 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7857 compose_select_account(compose, account, TRUE);
7859 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7860 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7862 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7863 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7865 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7866 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7868 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7869 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7871 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7872 if (account->protocol != A_NNTP)
7873 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7874 prefs_common_translated_header_name("To:"));
7876 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7877 prefs_common_translated_header_name("Newsgroups:"));
7879 #ifndef USE_NEW_ADDRBOOK
7880 addressbook_set_target_compose(compose);
7882 if (mode != COMPOSE_REDIRECT)
7883 compose_set_template_menu(compose);
7885 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7888 compose_list = g_list_append(compose_list, compose);
7890 if (!prefs_common.show_ruler)
7891 gtk_widget_hide(ruler_hbox);
7893 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7896 compose->priority = PRIORITY_NORMAL;
7897 compose_update_priority_menu_item(compose);
7899 compose_set_out_encoding(compose);
7902 compose_update_actions_menu(compose);
7904 /* Privacy Systems menu */
7905 compose_update_privacy_systems_menu(compose);
7907 activate_privacy_system(compose, account, TRUE);
7908 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7910 gtk_widget_realize(window);
7912 gtk_widget_show(window);
7914 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7915 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7922 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7927 GtkWidget *optmenubox;
7930 GtkWidget *from_name = NULL;
7931 #if !(GTK_CHECK_VERSION(2,12,0))
7932 GtkTooltips *tips = compose->tooltips;
7935 gint num = 0, def_menu = 0;
7937 accounts = account_get_list();
7938 cm_return_val_if_fail(accounts != NULL, NULL);
7940 optmenubox = gtk_event_box_new();
7941 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7942 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7944 hbox = gtk_hbox_new(FALSE, 6);
7945 from_name = gtk_entry_new();
7947 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7948 G_CALLBACK(compose_grab_focus_cb), compose);
7950 for (; accounts != NULL; accounts = accounts->next, num++) {
7951 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7952 gchar *name, *from = NULL;
7954 if (ac == compose->account) def_menu = num;
7956 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7959 if (ac == compose->account) {
7960 if (ac->name && *ac->name) {
7962 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7963 from = g_strdup_printf("%s <%s>",
7965 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7967 from = g_strdup_printf("%s",
7969 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7972 COMBOBOX_ADD(menu, name, ac->account_id);
7977 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7979 g_signal_connect(G_OBJECT(optmenu), "changed",
7980 G_CALLBACK(account_activated),
7982 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7983 G_CALLBACK(compose_entry_popup_extend),
7986 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7987 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7989 CLAWS_SET_TIP(optmenubox,
7990 _("Account to use for this email"));
7991 CLAWS_SET_TIP(from_name,
7992 _("Sender address to be used"));
7994 compose->account_combo = optmenu;
7995 compose->from_name = from_name;
8000 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8002 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8003 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8004 Compose *compose = (Compose *) data;
8006 compose->priority = value;
8010 static void compose_reply_change_mode(Compose *compose,
8013 gboolean was_modified = compose->modified;
8015 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8017 cm_return_if_fail(compose->replyinfo != NULL);
8019 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8021 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8023 if (action == COMPOSE_REPLY_TO_ALL)
8025 if (action == COMPOSE_REPLY_TO_SENDER)
8027 if (action == COMPOSE_REPLY_TO_LIST)
8030 compose_remove_header_entries(compose);
8031 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8032 if (compose->account->set_autocc && compose->account->auto_cc)
8033 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8035 if (compose->account->set_autobcc && compose->account->auto_bcc)
8036 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8038 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8039 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8040 compose_show_first_last_header(compose, TRUE);
8041 compose->modified = was_modified;
8042 compose_set_title(compose);
8045 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8047 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8048 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8049 Compose *compose = (Compose *) data;
8052 compose_reply_change_mode(compose, value);
8055 static void compose_update_priority_menu_item(Compose * compose)
8057 GtkWidget *menuitem = NULL;
8058 switch (compose->priority) {
8059 case PRIORITY_HIGHEST:
8060 menuitem = gtk_ui_manager_get_widget
8061 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8064 menuitem = gtk_ui_manager_get_widget
8065 (compose->ui_manager, "/Menu/Options/Priority/High");
8067 case PRIORITY_NORMAL:
8068 menuitem = gtk_ui_manager_get_widget
8069 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8072 menuitem = gtk_ui_manager_get_widget
8073 (compose->ui_manager, "/Menu/Options/Priority/Low");
8075 case PRIORITY_LOWEST:
8076 menuitem = gtk_ui_manager_get_widget
8077 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8080 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8083 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8085 Compose *compose = (Compose *) data;
8087 gboolean can_sign = FALSE, can_encrypt = FALSE;
8089 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8091 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8094 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8095 g_free(compose->privacy_system);
8096 compose->privacy_system = NULL;
8097 if (systemid != NULL) {
8098 compose->privacy_system = g_strdup(systemid);
8100 can_sign = privacy_system_can_sign(systemid);
8101 can_encrypt = privacy_system_can_encrypt(systemid);
8104 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8106 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8107 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8110 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8112 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8113 GtkWidget *menuitem = NULL;
8114 GList *children, *amenu;
8115 gboolean can_sign = FALSE, can_encrypt = FALSE;
8116 gboolean found = FALSE;
8118 if (compose->privacy_system != NULL) {
8120 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8121 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8122 cm_return_if_fail(menuitem != NULL);
8124 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8127 while (amenu != NULL) {
8128 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8129 if (systemid != NULL) {
8130 if (strcmp(systemid, compose->privacy_system) == 0 &&
8131 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8132 menuitem = GTK_WIDGET(amenu->data);
8134 can_sign = privacy_system_can_sign(systemid);
8135 can_encrypt = privacy_system_can_encrypt(systemid);
8139 } else if (strlen(compose->privacy_system) == 0 &&
8140 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8141 menuitem = GTK_WIDGET(amenu->data);
8144 can_encrypt = FALSE;
8149 amenu = amenu->next;
8151 g_list_free(children);
8152 if (menuitem != NULL)
8153 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8155 if (warn && !found && strlen(compose->privacy_system)) {
8156 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8157 "will not be able to sign or encrypt this message."),
8158 compose->privacy_system);
8162 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8163 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8166 static void compose_set_out_encoding(Compose *compose)
8168 CharSet out_encoding;
8169 const gchar *branch = NULL;
8170 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8172 switch(out_encoding) {
8173 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8174 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8175 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8176 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8177 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8178 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8179 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8180 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8181 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8182 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8183 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8184 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8185 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8186 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8187 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8188 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8189 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8190 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8191 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8192 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8193 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8194 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8195 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8196 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8197 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8198 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8199 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8200 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8201 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8202 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8203 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8204 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8205 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8207 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8210 static void compose_set_template_menu(Compose *compose)
8212 GSList *tmpl_list, *cur;
8216 tmpl_list = template_get_config();
8218 menu = gtk_menu_new();
8220 gtk_menu_set_accel_group (GTK_MENU (menu),
8221 gtk_ui_manager_get_accel_group(compose->ui_manager));
8222 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8223 Template *tmpl = (Template *)cur->data;
8224 gchar *accel_path = NULL;
8225 item = gtk_menu_item_new_with_label(tmpl->name);
8226 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8227 g_signal_connect(G_OBJECT(item), "activate",
8228 G_CALLBACK(compose_template_activate_cb),
8230 g_object_set_data(G_OBJECT(item), "template", tmpl);
8231 gtk_widget_show(item);
8232 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8233 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8237 gtk_widget_show(menu);
8238 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8241 void compose_update_actions_menu(Compose *compose)
8243 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8246 static void compose_update_privacy_systems_menu(Compose *compose)
8248 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8249 GSList *systems, *cur;
8251 GtkWidget *system_none;
8253 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8254 GtkWidget *privacy_menu = gtk_menu_new();
8256 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8257 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8259 g_signal_connect(G_OBJECT(system_none), "activate",
8260 G_CALLBACK(compose_set_privacy_system_cb), compose);
8262 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8263 gtk_widget_show(system_none);
8265 systems = privacy_get_system_ids();
8266 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8267 gchar *systemid = cur->data;
8269 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8270 widget = gtk_radio_menu_item_new_with_label(group,
8271 privacy_system_get_name(systemid));
8272 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8273 g_strdup(systemid), g_free);
8274 g_signal_connect(G_OBJECT(widget), "activate",
8275 G_CALLBACK(compose_set_privacy_system_cb), compose);
8277 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8278 gtk_widget_show(widget);
8281 g_slist_free(systems);
8282 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8283 gtk_widget_show_all(privacy_menu);
8284 gtk_widget_show_all(privacy_menuitem);
8287 void compose_reflect_prefs_all(void)
8292 for (cur = compose_list; cur != NULL; cur = cur->next) {
8293 compose = (Compose *)cur->data;
8294 compose_set_template_menu(compose);
8298 void compose_reflect_prefs_pixmap_theme(void)
8303 for (cur = compose_list; cur != NULL; cur = cur->next) {
8304 compose = (Compose *)cur->data;
8305 toolbar_update(TOOLBAR_COMPOSE, compose);
8309 static const gchar *compose_quote_char_from_context(Compose *compose)
8311 const gchar *qmark = NULL;
8313 cm_return_val_if_fail(compose != NULL, NULL);
8315 switch (compose->mode) {
8316 /* use forward-specific quote char */
8317 case COMPOSE_FORWARD:
8318 case COMPOSE_FORWARD_AS_ATTACH:
8319 case COMPOSE_FORWARD_INLINE:
8320 if (compose->folder && compose->folder->prefs &&
8321 compose->folder->prefs->forward_with_format)
8322 qmark = compose->folder->prefs->forward_quotemark;
8323 else if (compose->account->forward_with_format)
8324 qmark = compose->account->forward_quotemark;
8326 qmark = prefs_common.fw_quotemark;
8329 /* use reply-specific quote char in all other modes */
8331 if (compose->folder && compose->folder->prefs &&
8332 compose->folder->prefs->reply_with_format)
8333 qmark = compose->folder->prefs->reply_quotemark;
8334 else if (compose->account->reply_with_format)
8335 qmark = compose->account->reply_quotemark;
8337 qmark = prefs_common.quotemark;
8341 if (qmark == NULL || *qmark == '\0')
8347 static void compose_template_apply(Compose *compose, Template *tmpl,
8351 GtkTextBuffer *buffer;
8355 gchar *parsed_str = NULL;
8356 gint cursor_pos = 0;
8357 const gchar *err_msg = _("The body of the template has an error at line %d.");
8360 /* process the body */
8362 text = GTK_TEXT_VIEW(compose->text);
8363 buffer = gtk_text_view_get_buffer(text);
8366 qmark = compose_quote_char_from_context(compose);
8368 if (compose->replyinfo != NULL) {
8371 gtk_text_buffer_set_text(buffer, "", -1);
8372 mark = gtk_text_buffer_get_insert(buffer);
8373 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8375 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8376 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8378 } else if (compose->fwdinfo != NULL) {
8381 gtk_text_buffer_set_text(buffer, "", -1);
8382 mark = gtk_text_buffer_get_insert(buffer);
8383 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8385 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8386 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8389 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8391 GtkTextIter start, end;
8394 gtk_text_buffer_get_start_iter(buffer, &start);
8395 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8396 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8398 /* clear the buffer now */
8400 gtk_text_buffer_set_text(buffer, "", -1);
8402 parsed_str = compose_quote_fmt(compose, dummyinfo,
8403 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8404 procmsg_msginfo_free( dummyinfo );
8410 gtk_text_buffer_set_text(buffer, "", -1);
8411 mark = gtk_text_buffer_get_insert(buffer);
8412 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8415 if (replace && parsed_str && compose->account->auto_sig)
8416 compose_insert_sig(compose, FALSE);
8418 if (replace && parsed_str) {
8419 gtk_text_buffer_get_start_iter(buffer, &iter);
8420 gtk_text_buffer_place_cursor(buffer, &iter);
8424 cursor_pos = quote_fmt_get_cursor_pos();
8425 compose->set_cursor_pos = cursor_pos;
8426 if (cursor_pos == -1)
8428 gtk_text_buffer_get_start_iter(buffer, &iter);
8429 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8430 gtk_text_buffer_place_cursor(buffer, &iter);
8433 /* process the other fields */
8435 compose_template_apply_fields(compose, tmpl);
8436 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8437 quote_fmt_reset_vartable();
8438 compose_changed_cb(NULL, compose);
8441 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8442 gtkaspell_highlight_all(compose->gtkaspell);
8446 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8448 MsgInfo* dummyinfo = NULL;
8449 MsgInfo *msginfo = NULL;
8452 if (compose->replyinfo != NULL)
8453 msginfo = compose->replyinfo;
8454 else if (compose->fwdinfo != NULL)
8455 msginfo = compose->fwdinfo;
8457 dummyinfo = compose_msginfo_new_from_compose(compose);
8458 msginfo = dummyinfo;
8461 if (tmpl->from && *tmpl->from != '\0') {
8463 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8464 compose->gtkaspell);
8466 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8468 quote_fmt_scan_string(tmpl->from);
8471 buf = quote_fmt_get_buffer();
8473 alertpanel_error(_("Template From format error."));
8475 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8479 if (tmpl->to && *tmpl->to != '\0') {
8481 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8482 compose->gtkaspell);
8484 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8486 quote_fmt_scan_string(tmpl->to);
8489 buf = quote_fmt_get_buffer();
8491 alertpanel_error(_("Template To format error."));
8493 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8497 if (tmpl->cc && *tmpl->cc != '\0') {
8499 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8500 compose->gtkaspell);
8502 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8504 quote_fmt_scan_string(tmpl->cc);
8507 buf = quote_fmt_get_buffer();
8509 alertpanel_error(_("Template Cc format error."));
8511 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8515 if (tmpl->bcc && *tmpl->bcc != '\0') {
8517 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8518 compose->gtkaspell);
8520 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8522 quote_fmt_scan_string(tmpl->bcc);
8525 buf = quote_fmt_get_buffer();
8527 alertpanel_error(_("Template Bcc format error."));
8529 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8533 /* process the subject */
8534 if (tmpl->subject && *tmpl->subject != '\0') {
8536 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8537 compose->gtkaspell);
8539 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8541 quote_fmt_scan_string(tmpl->subject);
8544 buf = quote_fmt_get_buffer();
8546 alertpanel_error(_("Template subject format error."));
8548 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8552 procmsg_msginfo_free( dummyinfo );
8555 static void compose_destroy(Compose *compose)
8557 GtkAllocation allocation;
8558 GtkTextBuffer *buffer;
8559 GtkClipboard *clipboard;
8561 compose_list = g_list_remove(compose_list, compose);
8563 if (compose->updating) {
8564 debug_print("danger, not destroying anything now\n");
8565 compose->deferred_destroy = TRUE;
8568 /* NOTE: address_completion_end() does nothing with the window
8569 * however this may change. */
8570 address_completion_end(compose->window);
8572 slist_free_strings_full(compose->to_list);
8573 slist_free_strings_full(compose->newsgroup_list);
8574 slist_free_strings_full(compose->header_list);
8576 slist_free_strings_full(extra_headers);
8577 extra_headers = NULL;
8579 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8581 g_hash_table_destroy(compose->email_hashtable);
8583 procmsg_msginfo_free(compose->targetinfo);
8584 procmsg_msginfo_free(compose->replyinfo);
8585 procmsg_msginfo_free(compose->fwdinfo);
8587 g_free(compose->replyto);
8588 g_free(compose->cc);
8589 g_free(compose->bcc);
8590 g_free(compose->newsgroups);
8591 g_free(compose->followup_to);
8593 g_free(compose->ml_post);
8595 g_free(compose->inreplyto);
8596 g_free(compose->references);
8597 g_free(compose->msgid);
8598 g_free(compose->boundary);
8600 g_free(compose->redirect_filename);
8601 if (compose->undostruct)
8602 undo_destroy(compose->undostruct);
8604 g_free(compose->sig_str);
8606 g_free(compose->exteditor_file);
8608 g_free(compose->orig_charset);
8610 g_free(compose->privacy_system);
8612 #ifndef USE_NEW_ADDRBOOK
8613 if (addressbook_get_target_compose() == compose)
8614 addressbook_set_target_compose(NULL);
8617 if (compose->gtkaspell) {
8618 gtkaspell_delete(compose->gtkaspell);
8619 compose->gtkaspell = NULL;
8623 if (!compose->batch) {
8624 gtk_widget_get_allocation(compose->window, &allocation);
8625 prefs_common.compose_width = allocation.width;
8626 prefs_common.compose_height = allocation.height;
8629 if (!gtk_widget_get_parent(compose->paned))
8630 gtk_widget_destroy(compose->paned);
8631 gtk_widget_destroy(compose->popupmenu);
8633 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8634 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8635 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8637 gtk_widget_destroy(compose->window);
8638 toolbar_destroy(compose->toolbar);
8639 g_free(compose->toolbar);
8640 cm_mutex_free(compose->mutex);
8644 static void compose_attach_info_free(AttachInfo *ainfo)
8646 g_free(ainfo->file);
8647 g_free(ainfo->content_type);
8648 g_free(ainfo->name);
8649 g_free(ainfo->charset);
8653 static void compose_attach_update_label(Compose *compose)
8658 GtkTreeModel *model;
8663 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8664 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8665 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8669 while(gtk_tree_model_iter_next(model, &iter))
8672 text = g_strdup_printf("(%d)", i);
8673 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8677 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8679 Compose *compose = (Compose *)data;
8680 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8681 GtkTreeSelection *selection;
8683 GtkTreeModel *model;
8685 selection = gtk_tree_view_get_selection(tree_view);
8686 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8691 for (cur = sel; cur != NULL; cur = cur->next) {
8692 GtkTreePath *path = cur->data;
8693 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8696 gtk_tree_path_free(path);
8699 for (cur = sel; cur != NULL; cur = cur->next) {
8700 GtkTreeRowReference *ref = cur->data;
8701 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8704 if (gtk_tree_model_get_iter(model, &iter, path))
8705 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8707 gtk_tree_path_free(path);
8708 gtk_tree_row_reference_free(ref);
8712 compose_attach_update_label(compose);
8715 static struct _AttachProperty
8718 GtkWidget *mimetype_entry;
8719 GtkWidget *encoding_optmenu;
8720 GtkWidget *path_entry;
8721 GtkWidget *filename_entry;
8723 GtkWidget *cancel_btn;
8726 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8728 gtk_tree_path_free((GtkTreePath *)ptr);
8731 static void compose_attach_property(GtkAction *action, gpointer data)
8733 Compose *compose = (Compose *)data;
8734 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8736 GtkComboBox *optmenu;
8737 GtkTreeSelection *selection;
8739 GtkTreeModel *model;
8742 static gboolean cancelled;
8744 /* only if one selected */
8745 selection = gtk_tree_view_get_selection(tree_view);
8746 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8749 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8753 path = (GtkTreePath *) sel->data;
8754 gtk_tree_model_get_iter(model, &iter, path);
8755 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8758 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8764 if (!attach_prop.window)
8765 compose_attach_property_create(&cancelled);
8766 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8767 gtk_widget_grab_focus(attach_prop.ok_btn);
8768 gtk_widget_show(attach_prop.window);
8769 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8770 GTK_WINDOW(compose->window));
8772 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8773 if (ainfo->encoding == ENC_UNKNOWN)
8774 combobox_select_by_data(optmenu, ENC_BASE64);
8776 combobox_select_by_data(optmenu, ainfo->encoding);
8778 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8779 ainfo->content_type ? ainfo->content_type : "");
8780 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8781 ainfo->file ? ainfo->file : "");
8782 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8783 ainfo->name ? ainfo->name : "");
8786 const gchar *entry_text;
8788 gchar *cnttype = NULL;
8795 gtk_widget_hide(attach_prop.window);
8796 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8801 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8802 if (*entry_text != '\0') {
8805 text = g_strstrip(g_strdup(entry_text));
8806 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8807 cnttype = g_strdup(text);
8810 alertpanel_error(_("Invalid MIME type."));
8816 ainfo->encoding = combobox_get_active_data(optmenu);
8818 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8819 if (*entry_text != '\0') {
8820 if (is_file_exist(entry_text) &&
8821 (size = get_file_size(entry_text)) > 0)
8822 file = g_strdup(entry_text);
8825 (_("File doesn't exist or is empty."));
8831 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8832 if (*entry_text != '\0') {
8833 g_free(ainfo->name);
8834 ainfo->name = g_strdup(entry_text);
8838 g_free(ainfo->content_type);
8839 ainfo->content_type = cnttype;
8842 g_free(ainfo->file);
8846 ainfo->size = (goffset)size;
8848 /* update tree store */
8849 text = to_human_readable(ainfo->size);
8850 gtk_tree_model_get_iter(model, &iter, path);
8851 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8852 COL_MIMETYPE, ainfo->content_type,
8854 COL_NAME, ainfo->name,
8855 COL_CHARSET, ainfo->charset,
8861 gtk_tree_path_free(path);
8864 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8866 label = gtk_label_new(str); \
8867 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8868 GTK_FILL, 0, 0, 0); \
8869 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8871 entry = gtk_entry_new(); \
8872 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8873 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8876 static void compose_attach_property_create(gboolean *cancelled)
8882 GtkWidget *mimetype_entry;
8885 GtkListStore *optmenu_menu;
8886 GtkWidget *path_entry;
8887 GtkWidget *filename_entry;
8890 GtkWidget *cancel_btn;
8891 GList *mime_type_list, *strlist;
8894 debug_print("Creating attach_property window...\n");
8896 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8897 gtk_widget_set_size_request(window, 480, -1);
8898 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8899 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8900 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8901 g_signal_connect(G_OBJECT(window), "delete_event",
8902 G_CALLBACK(attach_property_delete_event),
8904 g_signal_connect(G_OBJECT(window), "key_press_event",
8905 G_CALLBACK(attach_property_key_pressed),
8908 vbox = gtk_vbox_new(FALSE, 8);
8909 gtk_container_add(GTK_CONTAINER(window), vbox);
8911 table = gtk_table_new(4, 2, FALSE);
8912 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8913 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8914 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8916 label = gtk_label_new(_("MIME type"));
8917 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8919 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8920 #if !GTK_CHECK_VERSION(2, 24, 0)
8921 mimetype_entry = gtk_combo_box_entry_new_text();
8923 mimetype_entry = gtk_combo_box_text_new_with_entry();
8925 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8926 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8928 /* stuff with list */
8929 mime_type_list = procmime_get_mime_type_list();
8931 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8932 MimeType *type = (MimeType *) mime_type_list->data;
8935 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8937 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8940 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8941 (GCompareFunc)strcmp2);
8944 for (mime_type_list = strlist; mime_type_list != NULL;
8945 mime_type_list = mime_type_list->next) {
8946 #if !GTK_CHECK_VERSION(2, 24, 0)
8947 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8949 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8951 g_free(mime_type_list->data);
8953 g_list_free(strlist);
8954 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8955 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8957 label = gtk_label_new(_("Encoding"));
8958 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8960 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8962 hbox = gtk_hbox_new(FALSE, 0);
8963 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8964 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8966 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8967 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8969 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8970 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8971 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8972 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8973 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8975 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8977 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8978 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8980 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8981 &ok_btn, GTK_STOCK_OK,
8983 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8984 gtk_widget_grab_default(ok_btn);
8986 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8987 G_CALLBACK(attach_property_ok),
8989 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8990 G_CALLBACK(attach_property_cancel),
8993 gtk_widget_show_all(vbox);
8995 attach_prop.window = window;
8996 attach_prop.mimetype_entry = mimetype_entry;
8997 attach_prop.encoding_optmenu = optmenu;
8998 attach_prop.path_entry = path_entry;
8999 attach_prop.filename_entry = filename_entry;
9000 attach_prop.ok_btn = ok_btn;
9001 attach_prop.cancel_btn = cancel_btn;
9004 #undef SET_LABEL_AND_ENTRY
9006 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9012 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9018 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9019 gboolean *cancelled)
9027 static gboolean attach_property_key_pressed(GtkWidget *widget,
9029 gboolean *cancelled)
9031 if (event && event->keyval == GDK_KEY_Escape) {
9035 if (event && event->keyval == GDK_KEY_Return) {
9043 static void compose_exec_ext_editor(Compose *compose)
9050 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9051 G_DIR_SEPARATOR, compose);
9053 if (pipe(pipe_fds) < 0) {
9059 if ((pid = fork()) < 0) {
9066 /* close the write side of the pipe */
9069 compose->exteditor_file = g_strdup(tmp);
9070 compose->exteditor_pid = pid;
9072 compose_set_ext_editor_sensitive(compose, FALSE);
9075 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9077 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9079 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9083 } else { /* process-monitoring process */
9089 /* close the read side of the pipe */
9092 if (compose_write_body_to_file(compose, tmp) < 0) {
9093 fd_write_all(pipe_fds[1], "2\n", 2);
9097 pid_ed = compose_exec_ext_editor_real(tmp);
9099 fd_write_all(pipe_fds[1], "1\n", 2);
9103 /* wait until editor is terminated */
9104 waitpid(pid_ed, NULL, 0);
9106 fd_write_all(pipe_fds[1], "0\n", 2);
9113 #endif /* G_OS_UNIX */
9117 static gint compose_exec_ext_editor_real(const gchar *file)
9124 cm_return_val_if_fail(file != NULL, -1);
9126 if ((pid = fork()) < 0) {
9131 if (pid != 0) return pid;
9133 /* grandchild process */
9135 if (setpgid(0, getppid()))
9138 if (prefs_common_get_ext_editor_cmd() &&
9139 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9140 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9141 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9143 if (prefs_common_get_ext_editor_cmd())
9144 g_warning("External editor command-line is invalid: '%s'\n",
9145 prefs_common_get_ext_editor_cmd());
9146 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9149 cmdline = strsplit_with_quote(buf, " ", 1024);
9150 execvp(cmdline[0], cmdline);
9153 g_strfreev(cmdline);
9158 static gboolean compose_ext_editor_kill(Compose *compose)
9160 pid_t pgid = compose->exteditor_pid * -1;
9163 ret = kill(pgid, 0);
9165 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9169 msg = g_strdup_printf
9170 (_("The external editor is still working.\n"
9171 "Force terminating the process?\n"
9172 "process group id: %d"), -pgid);
9173 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9174 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9178 if (val == G_ALERTALTERNATE) {
9179 g_source_remove(compose->exteditor_tag);
9180 g_io_channel_shutdown(compose->exteditor_ch,
9182 g_io_channel_unref(compose->exteditor_ch);
9184 if (kill(pgid, SIGTERM) < 0) perror("kill");
9185 waitpid(compose->exteditor_pid, NULL, 0);
9187 g_warning("Terminated process group id: %d", -pgid);
9188 g_warning("Temporary file: %s",
9189 compose->exteditor_file);
9191 compose_set_ext_editor_sensitive(compose, TRUE);
9193 g_free(compose->exteditor_file);
9194 compose->exteditor_file = NULL;
9195 compose->exteditor_pid = -1;
9196 compose->exteditor_ch = NULL;
9197 compose->exteditor_tag = -1;
9205 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9209 Compose *compose = (Compose *)data;
9212 debug_print("Compose: input from monitoring process\n");
9214 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9216 g_io_channel_shutdown(source, FALSE, NULL);
9217 g_io_channel_unref(source);
9219 waitpid(compose->exteditor_pid, NULL, 0);
9221 if (buf[0] == '0') { /* success */
9222 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9223 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9225 gtk_text_buffer_set_text(buffer, "", -1);
9226 compose_insert_file(compose, compose->exteditor_file);
9227 compose_changed_cb(NULL, compose);
9228 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9230 if (claws_unlink(compose->exteditor_file) < 0)
9231 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9232 } else if (buf[0] == '1') { /* failed */
9233 g_warning("Couldn't exec external editor\n");
9234 if (claws_unlink(compose->exteditor_file) < 0)
9235 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9236 } else if (buf[0] == '2') {
9237 g_warning("Couldn't write to file\n");
9238 } else if (buf[0] == '3') {
9239 g_warning("Pipe read failed\n");
9242 compose_set_ext_editor_sensitive(compose, TRUE);
9244 g_free(compose->exteditor_file);
9245 compose->exteditor_file = NULL;
9246 compose->exteditor_pid = -1;
9247 compose->exteditor_ch = NULL;
9248 compose->exteditor_tag = -1;
9253 static void compose_set_ext_editor_sensitive(Compose *compose,
9256 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9257 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9258 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9259 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9260 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9261 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9262 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9264 gtk_widget_set_sensitive(compose->text, sensitive);
9265 if (compose->toolbar->send_btn)
9266 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9267 if (compose->toolbar->sendl_btn)
9268 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9269 if (compose->toolbar->draft_btn)
9270 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9271 if (compose->toolbar->insert_btn)
9272 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9273 if (compose->toolbar->sig_btn)
9274 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9275 if (compose->toolbar->exteditor_btn)
9276 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9277 if (compose->toolbar->linewrap_current_btn)
9278 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9279 if (compose->toolbar->linewrap_all_btn)
9280 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9282 #endif /* G_OS_UNIX */
9285 * compose_undo_state_changed:
9287 * Change the sensivity of the menuentries undo and redo
9289 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9290 gint redo_state, gpointer data)
9292 Compose *compose = (Compose *)data;
9294 switch (undo_state) {
9295 case UNDO_STATE_TRUE:
9296 if (!undostruct->undo_state) {
9297 undostruct->undo_state = TRUE;
9298 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9301 case UNDO_STATE_FALSE:
9302 if (undostruct->undo_state) {
9303 undostruct->undo_state = FALSE;
9304 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9307 case UNDO_STATE_UNCHANGED:
9309 case UNDO_STATE_REFRESH:
9310 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9313 g_warning("Undo state not recognized");
9317 switch (redo_state) {
9318 case UNDO_STATE_TRUE:
9319 if (!undostruct->redo_state) {
9320 undostruct->redo_state = TRUE;
9321 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9324 case UNDO_STATE_FALSE:
9325 if (undostruct->redo_state) {
9326 undostruct->redo_state = FALSE;
9327 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9330 case UNDO_STATE_UNCHANGED:
9332 case UNDO_STATE_REFRESH:
9333 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9336 g_warning("Redo state not recognized");
9341 /* callback functions */
9343 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9344 GtkAllocation *allocation,
9347 prefs_common.compose_notebook_height = allocation->height;
9350 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9351 * includes "non-client" (windows-izm) in calculation, so this calculation
9352 * may not be accurate.
9354 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9355 GtkAllocation *allocation,
9356 GtkSHRuler *shruler)
9358 if (prefs_common.show_ruler) {
9359 gint char_width = 0, char_height = 0;
9360 gint line_width_in_chars;
9362 gtkut_get_font_size(GTK_WIDGET(widget),
9363 &char_width, &char_height);
9364 line_width_in_chars =
9365 (allocation->width - allocation->x) / char_width;
9367 /* got the maximum */
9368 gtk_shruler_set_range(GTK_SHRULER(shruler),
9369 0.0, line_width_in_chars, 0);
9378 ComposePrefType type;
9379 gboolean entry_marked;
9382 static void account_activated(GtkComboBox *optmenu, gpointer data)
9384 Compose *compose = (Compose *)data;
9387 gchar *folderidentifier;
9388 gint account_id = 0;
9391 GSList *list, *saved_list = NULL;
9392 HeaderEntryState *state;
9393 GtkRcStyle *style = NULL;
9394 #if !GTK_CHECK_VERSION(3, 0, 0)
9395 static GdkColor yellow;
9396 static gboolean color_set = FALSE;
9398 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9401 /* Get ID of active account in the combo box */
9402 menu = gtk_combo_box_get_model(optmenu);
9403 gtk_combo_box_get_active_iter(optmenu, &iter);
9404 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9406 ac = account_find_from_id(account_id);
9407 cm_return_if_fail(ac != NULL);
9409 if (ac != compose->account) {
9410 compose_select_account(compose, ac, FALSE);
9412 for (list = compose->header_list; list; list = list->next) {
9413 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9415 if (hentry->type == PREF_ACCOUNT || !list->next) {
9416 compose_destroy_headerentry(compose, hentry);
9420 state = g_malloc0(sizeof(HeaderEntryState));
9421 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9422 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9423 state->entry = gtk_editable_get_chars(
9424 GTK_EDITABLE(hentry->entry), 0, -1);
9425 state->type = hentry->type;
9427 #if !GTK_CHECK_VERSION(3, 0, 0)
9429 gdk_color_parse("#f5f6be", &yellow);
9430 color_set = gdk_colormap_alloc_color(
9431 gdk_colormap_get_system(),
9432 &yellow, FALSE, TRUE);
9436 style = gtk_widget_get_modifier_style(hentry->entry);
9437 state->entry_marked = gdk_color_equal(&yellow,
9438 &style->base[GTK_STATE_NORMAL]);
9440 saved_list = g_slist_append(saved_list, state);
9441 compose_destroy_headerentry(compose, hentry);
9444 compose->header_last = NULL;
9445 g_slist_free(compose->header_list);
9446 compose->header_list = NULL;
9447 compose->header_nextrow = 1;
9448 compose_create_header_entry(compose);
9450 if (ac->set_autocc && ac->auto_cc)
9451 compose_entry_append(compose, ac->auto_cc,
9452 COMPOSE_CC, PREF_ACCOUNT);
9454 if (ac->set_autobcc && ac->auto_bcc)
9455 compose_entry_append(compose, ac->auto_bcc,
9456 COMPOSE_BCC, PREF_ACCOUNT);
9458 if (ac->set_autoreplyto && ac->auto_replyto)
9459 compose_entry_append(compose, ac->auto_replyto,
9460 COMPOSE_REPLYTO, PREF_ACCOUNT);
9462 for (list = saved_list; list; list = list->next) {
9463 state = (HeaderEntryState *) list->data;
9465 compose_add_header_entry(compose, state->header,
9466 state->entry, state->type);
9467 if (state->entry_marked)
9468 compose_entry_mark_default_to(compose, state->entry);
9470 g_free(state->header);
9471 g_free(state->entry);
9474 g_slist_free(saved_list);
9476 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9477 (ac->protocol == A_NNTP) ?
9478 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9481 /* Set message save folder */
9482 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9483 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9485 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9486 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9488 compose_set_save_to(compose, NULL);
9489 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9490 folderidentifier = folder_item_get_identifier(account_get_special_folder
9491 (compose->account, F_OUTBOX));
9492 compose_set_save_to(compose, folderidentifier);
9493 g_free(folderidentifier);
9497 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9498 GtkTreeViewColumn *column, Compose *compose)
9500 compose_attach_property(NULL, compose);
9503 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9506 Compose *compose = (Compose *)data;
9507 GtkTreeSelection *attach_selection;
9508 gint attach_nr_selected;
9510 if (!event) return FALSE;
9512 if (event->button == 3) {
9513 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9514 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9516 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9517 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9519 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9520 NULL, NULL, event->button, event->time);
9527 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9530 Compose *compose = (Compose *)data;
9532 if (!event) return FALSE;
9534 switch (event->keyval) {
9535 case GDK_KEY_Delete:
9536 compose_attach_remove_selected(NULL, compose);
9542 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9544 toolbar_comp_set_sensitive(compose, allow);
9545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9546 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9550 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9551 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9554 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9558 static void compose_send_cb(GtkAction *action, gpointer data)
9560 Compose *compose = (Compose *)data;
9562 if (prefs_common.work_offline &&
9563 !inc_offline_should_override(TRUE,
9564 _("Claws Mail needs network access in order "
9565 "to send this email.")))
9568 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9569 g_source_remove(compose->draft_timeout_tag);
9570 compose->draft_timeout_tag = -1;
9573 compose_send(compose);
9576 static void compose_send_later_cb(GtkAction *action, gpointer data)
9578 Compose *compose = (Compose *)data;
9582 compose_allow_user_actions(compose, FALSE);
9583 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9584 compose_allow_user_actions(compose, TRUE);
9588 compose_close(compose);
9589 } else if (val == -1) {
9590 alertpanel_error(_("Could not queue message."));
9591 } else if (val == -2) {
9592 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9593 } else if (val == -3) {
9594 if (privacy_peek_error())
9595 alertpanel_error(_("Could not queue message for sending:\n\n"
9596 "Signature failed: %s"), privacy_get_error());
9597 } else if (val == -4) {
9598 alertpanel_error(_("Could not queue message for sending:\n\n"
9599 "Charset conversion failed."));
9600 } else if (val == -5) {
9601 alertpanel_error(_("Could not queue message for sending:\n\n"
9602 "Couldn't get recipient encryption key."));
9603 } else if (val == -6) {
9606 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9609 #define DRAFTED_AT_EXIT "drafted_at_exit"
9610 static void compose_register_draft(MsgInfo *info)
9612 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9613 DRAFTED_AT_EXIT, NULL);
9614 FILE *fp = g_fopen(filepath, "ab");
9617 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9625 gboolean compose_draft (gpointer data, guint action)
9627 Compose *compose = (Compose *)data;
9632 MsgFlags flag = {0, 0};
9633 static gboolean lock = FALSE;
9634 MsgInfo *newmsginfo;
9636 gboolean target_locked = FALSE;
9637 gboolean err = FALSE;
9639 if (lock) return FALSE;
9641 if (compose->sending)
9644 draft = account_get_special_folder(compose->account, F_DRAFT);
9645 cm_return_val_if_fail(draft != NULL, FALSE);
9647 if (!g_mutex_trylock(compose->mutex)) {
9648 /* we don't want to lock the mutex once it's available,
9649 * because as the only other part of compose.c locking
9650 * it is compose_close - which means once unlocked,
9651 * the compose struct will be freed */
9652 debug_print("couldn't lock mutex, probably sending\n");
9658 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9659 G_DIR_SEPARATOR, compose);
9660 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9661 FILE_OP_ERROR(tmp, "fopen");
9665 /* chmod for security */
9666 if (change_file_mode_rw(fp, tmp) < 0) {
9667 FILE_OP_ERROR(tmp, "chmod");
9668 g_warning("can't change file mode\n");
9671 /* Save draft infos */
9672 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9673 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9675 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9676 gchar *savefolderid;
9678 savefolderid = compose_get_save_to(compose);
9679 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9680 g_free(savefolderid);
9682 if (compose->return_receipt) {
9683 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9685 if (compose->privacy_system) {
9686 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9687 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9688 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9691 /* Message-ID of message replying to */
9692 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9695 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9696 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9699 /* Message-ID of message forwarding to */
9700 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9703 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9704 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9708 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9709 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9711 sheaders = compose_get_manual_headers_info(compose);
9712 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9715 /* end of headers */
9716 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9723 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9727 if (fclose(fp) == EOF) {
9731 if (compose->targetinfo) {
9732 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9733 flag.perm_flags = target_locked?MSG_LOCKED:0;
9735 flag.tmp_flags = MSG_DRAFT;
9737 folder_item_scan(draft);
9738 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9739 MsgInfo *tmpinfo = NULL;
9740 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9741 if (compose->msgid) {
9742 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9745 msgnum = tmpinfo->msgnum;
9746 procmsg_msginfo_free(tmpinfo);
9747 debug_print("got draft msgnum %d from scanning\n", msgnum);
9749 debug_print("didn't get draft msgnum after scanning\n");
9752 debug_print("got draft msgnum %d from adding\n", msgnum);
9758 if (action != COMPOSE_AUTO_SAVE) {
9759 if (action != COMPOSE_DRAFT_FOR_EXIT)
9760 alertpanel_error(_("Could not save draft."));
9763 gtkut_window_popup(compose->window);
9764 val = alertpanel_full(_("Could not save draft"),
9765 _("Could not save draft.\n"
9766 "Do you want to cancel exit or discard this email?"),
9767 _("_Cancel exit"), _("_Discard email"), NULL,
9768 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9769 if (val == G_ALERTALTERNATE) {
9771 g_mutex_unlock(compose->mutex); /* must be done before closing */
9772 compose_close(compose);
9776 g_mutex_unlock(compose->mutex); /* must be done before closing */
9785 if (compose->mode == COMPOSE_REEDIT) {
9786 compose_remove_reedit_target(compose, TRUE);
9789 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9792 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9794 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9796 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9797 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9798 procmsg_msginfo_set_flags(newmsginfo, 0,
9799 MSG_HAS_ATTACHMENT);
9801 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9802 compose_register_draft(newmsginfo);
9804 procmsg_msginfo_free(newmsginfo);
9807 folder_item_scan(draft);
9809 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9811 g_mutex_unlock(compose->mutex); /* must be done before closing */
9812 compose_close(compose);
9818 path = folder_item_fetch_msg(draft, msgnum);
9820 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9823 if (g_stat(path, &s) < 0) {
9824 FILE_OP_ERROR(path, "stat");
9830 procmsg_msginfo_free(compose->targetinfo);
9831 compose->targetinfo = procmsg_msginfo_new();
9832 compose->targetinfo->msgnum = msgnum;
9833 compose->targetinfo->size = (goffset)s.st_size;
9834 compose->targetinfo->mtime = s.st_mtime;
9835 compose->targetinfo->folder = draft;
9837 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9838 compose->mode = COMPOSE_REEDIT;
9840 if (action == COMPOSE_AUTO_SAVE) {
9841 compose->autosaved_draft = compose->targetinfo;
9843 compose->modified = FALSE;
9844 compose_set_title(compose);
9848 g_mutex_unlock(compose->mutex);
9852 void compose_clear_exit_drafts(void)
9854 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9855 DRAFTED_AT_EXIT, NULL);
9856 if (is_file_exist(filepath))
9857 claws_unlink(filepath);
9862 void compose_reopen_exit_drafts(void)
9864 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9865 DRAFTED_AT_EXIT, NULL);
9866 FILE *fp = g_fopen(filepath, "rb");
9870 while (fgets(buf, sizeof(buf), fp)) {
9871 gchar **parts = g_strsplit(buf, "\t", 2);
9872 const gchar *folder = parts[0];
9873 int msgnum = parts[1] ? atoi(parts[1]):-1;
9875 if (folder && *folder && msgnum > -1) {
9876 FolderItem *item = folder_find_item_from_identifier(folder);
9877 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9879 compose_reedit(info, FALSE);
9886 compose_clear_exit_drafts();
9889 static void compose_save_cb(GtkAction *action, gpointer data)
9891 Compose *compose = (Compose *)data;
9892 compose_draft(compose, COMPOSE_KEEP_EDITING);
9893 compose->rmode = COMPOSE_REEDIT;
9896 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9898 if (compose && file_list) {
9901 for ( tmp = file_list; tmp; tmp = tmp->next) {
9902 gchar *file = (gchar *) tmp->data;
9903 gchar *utf8_filename = conv_filename_to_utf8(file);
9904 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9905 compose_changed_cb(NULL, compose);
9910 g_free(utf8_filename);
9915 static void compose_attach_cb(GtkAction *action, gpointer data)
9917 Compose *compose = (Compose *)data;
9920 if (compose->redirect_filename != NULL)
9923 /* Set focus_window properly, in case we were called via popup menu,
9924 * which unsets it (via focus_out_event callback on compose window). */
9925 manage_window_focus_in(compose->window, NULL, NULL);
9927 file_list = filesel_select_multiple_files_open(_("Select file"));
9930 compose_attach_from_list(compose, file_list, TRUE);
9931 g_list_free(file_list);
9935 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9937 Compose *compose = (Compose *)data;
9939 gint files_inserted = 0;
9941 file_list = filesel_select_multiple_files_open(_("Select file"));
9946 for ( tmp = file_list; tmp; tmp = tmp->next) {
9947 gchar *file = (gchar *) tmp->data;
9948 gchar *filedup = g_strdup(file);
9949 gchar *shortfile = g_path_get_basename(filedup);
9950 ComposeInsertResult res;
9951 /* insert the file if the file is short or if the user confirmed that
9952 he/she wants to insert the large file */
9953 res = compose_insert_file(compose, file);
9954 if (res == COMPOSE_INSERT_READ_ERROR) {
9955 alertpanel_error(_("File '%s' could not be read."), shortfile);
9956 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9957 alertpanel_error(_("File '%s' contained invalid characters\n"
9958 "for the current encoding, insertion may be incorrect."),
9960 } else if (res == COMPOSE_INSERT_SUCCESS)
9967 g_list_free(file_list);
9971 if (files_inserted > 0 && compose->gtkaspell &&
9972 compose->gtkaspell->check_while_typing)
9973 gtkaspell_highlight_all(compose->gtkaspell);
9977 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9979 Compose *compose = (Compose *)data;
9981 compose_insert_sig(compose, FALSE);
9984 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9988 Compose *compose = (Compose *)data;
9990 gtkut_widget_get_uposition(widget, &x, &y);
9991 if (!compose->batch) {
9992 prefs_common.compose_x = x;
9993 prefs_common.compose_y = y;
9995 if (compose->sending || compose->updating)
9997 compose_close_cb(NULL, compose);
10001 void compose_close_toolbar(Compose *compose)
10003 compose_close_cb(NULL, compose);
10006 static void compose_close_cb(GtkAction *action, gpointer data)
10008 Compose *compose = (Compose *)data;
10012 if (compose->exteditor_tag != -1) {
10013 if (!compose_ext_editor_kill(compose))
10018 if (compose->modified) {
10019 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10020 if (!g_mutex_trylock(compose->mutex)) {
10021 /* we don't want to lock the mutex once it's available,
10022 * because as the only other part of compose.c locking
10023 * it is compose_close - which means once unlocked,
10024 * the compose struct will be freed */
10025 debug_print("couldn't lock mutex, probably sending\n");
10029 val = alertpanel(_("Discard message"),
10030 _("This message has been modified. Discard it?"),
10031 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10033 val = alertpanel(_("Save changes"),
10034 _("This message has been modified. Save the latest changes?"),
10035 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10037 g_mutex_unlock(compose->mutex);
10039 case G_ALERTDEFAULT:
10040 if (prefs_common.autosave && !reedit)
10041 compose_remove_draft(compose);
10043 case G_ALERTALTERNATE:
10044 compose_draft(data, COMPOSE_QUIT_EDITING);
10051 compose_close(compose);
10054 static void compose_print_cb(GtkAction *action, gpointer data)
10056 Compose *compose = (Compose *) data;
10058 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10059 if (compose->targetinfo)
10060 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10063 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10065 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10066 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10067 Compose *compose = (Compose *) data;
10070 compose->out_encoding = (CharSet)value;
10073 static void compose_address_cb(GtkAction *action, gpointer data)
10075 Compose *compose = (Compose *)data;
10077 #ifndef USE_NEW_ADDRBOOK
10078 addressbook_open(compose);
10080 GError* error = NULL;
10081 addressbook_connect_signals(compose);
10082 addressbook_dbus_open(TRUE, &error);
10084 g_warning("%s", error->message);
10085 g_error_free(error);
10090 static void about_show_cb(GtkAction *action, gpointer data)
10095 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10097 Compose *compose = (Compose *)data;
10102 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10103 cm_return_if_fail(tmpl != NULL);
10105 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10107 val = alertpanel(_("Apply template"), msg,
10108 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10111 if (val == G_ALERTDEFAULT)
10112 compose_template_apply(compose, tmpl, TRUE);
10113 else if (val == G_ALERTALTERNATE)
10114 compose_template_apply(compose, tmpl, FALSE);
10117 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10119 Compose *compose = (Compose *)data;
10121 compose_exec_ext_editor(compose);
10124 static void compose_undo_cb(GtkAction *action, gpointer data)
10126 Compose *compose = (Compose *)data;
10127 gboolean prev_autowrap = compose->autowrap;
10129 compose->autowrap = FALSE;
10130 undo_undo(compose->undostruct);
10131 compose->autowrap = prev_autowrap;
10134 static void compose_redo_cb(GtkAction *action, gpointer data)
10136 Compose *compose = (Compose *)data;
10137 gboolean prev_autowrap = compose->autowrap;
10139 compose->autowrap = FALSE;
10140 undo_redo(compose->undostruct);
10141 compose->autowrap = prev_autowrap;
10144 static void entry_cut_clipboard(GtkWidget *entry)
10146 if (GTK_IS_EDITABLE(entry))
10147 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10148 else if (GTK_IS_TEXT_VIEW(entry))
10149 gtk_text_buffer_cut_clipboard(
10150 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10151 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10155 static void entry_copy_clipboard(GtkWidget *entry)
10157 if (GTK_IS_EDITABLE(entry))
10158 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10159 else if (GTK_IS_TEXT_VIEW(entry))
10160 gtk_text_buffer_copy_clipboard(
10161 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10162 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10165 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10166 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10168 if (GTK_IS_TEXT_VIEW(entry)) {
10169 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10170 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10171 GtkTextIter start_iter, end_iter;
10173 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10175 if (contents == NULL)
10178 /* we shouldn't delete the selection when middle-click-pasting, or we
10179 * can't mid-click-paste our own selection */
10180 if (clip != GDK_SELECTION_PRIMARY) {
10181 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10182 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10185 if (insert_place == NULL) {
10186 /* if insert_place isn't specified, insert at the cursor.
10187 * used for Ctrl-V pasting */
10188 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10189 start = gtk_text_iter_get_offset(&start_iter);
10190 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10192 /* if insert_place is specified, paste here.
10193 * used for mid-click-pasting */
10194 start = gtk_text_iter_get_offset(insert_place);
10195 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10196 if (prefs_common.primary_paste_unselects)
10197 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10201 /* paste unwrapped: mark the paste so it's not wrapped later */
10202 end = start + strlen(contents);
10203 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10204 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10205 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10206 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10207 /* rewrap paragraph now (after a mid-click-paste) */
10208 mark_start = gtk_text_buffer_get_insert(buffer);
10209 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10210 gtk_text_iter_backward_char(&start_iter);
10211 compose_beautify_paragraph(compose, &start_iter, TRUE);
10213 } else if (GTK_IS_EDITABLE(entry))
10214 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10216 compose->modified = TRUE;
10219 static void entry_allsel(GtkWidget *entry)
10221 if (GTK_IS_EDITABLE(entry))
10222 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10223 else if (GTK_IS_TEXT_VIEW(entry)) {
10224 GtkTextIter startiter, enditer;
10225 GtkTextBuffer *textbuf;
10227 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10228 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10229 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10231 gtk_text_buffer_move_mark_by_name(textbuf,
10232 "selection_bound", &startiter);
10233 gtk_text_buffer_move_mark_by_name(textbuf,
10234 "insert", &enditer);
10238 static void compose_cut_cb(GtkAction *action, gpointer data)
10240 Compose *compose = (Compose *)data;
10241 if (compose->focused_editable
10242 #ifndef GENERIC_UMPC
10243 && gtk_widget_has_focus(compose->focused_editable)
10246 entry_cut_clipboard(compose->focused_editable);
10249 static void compose_copy_cb(GtkAction *action, gpointer data)
10251 Compose *compose = (Compose *)data;
10252 if (compose->focused_editable
10253 #ifndef GENERIC_UMPC
10254 && gtk_widget_has_focus(compose->focused_editable)
10257 entry_copy_clipboard(compose->focused_editable);
10260 static void compose_paste_cb(GtkAction *action, gpointer data)
10262 Compose *compose = (Compose *)data;
10263 gint prev_autowrap;
10264 GtkTextBuffer *buffer;
10266 if (compose->focused_editable &&
10267 #ifndef GENERIC_UMPC
10268 gtk_widget_has_focus(compose->focused_editable)
10271 entry_paste_clipboard(compose, compose->focused_editable,
10272 prefs_common.linewrap_pastes,
10273 GDK_SELECTION_CLIPBOARD, NULL);
10278 #ifndef GENERIC_UMPC
10279 gtk_widget_has_focus(compose->text) &&
10281 compose->gtkaspell &&
10282 compose->gtkaspell->check_while_typing)
10283 gtkaspell_highlight_all(compose->gtkaspell);
10287 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10289 Compose *compose = (Compose *)data;
10290 gint wrap_quote = prefs_common.linewrap_quote;
10291 if (compose->focused_editable
10292 #ifndef GENERIC_UMPC
10293 && gtk_widget_has_focus(compose->focused_editable)
10296 /* let text_insert() (called directly or at a later time
10297 * after the gtk_editable_paste_clipboard) know that
10298 * text is to be inserted as a quotation. implemented
10299 * by using a simple refcount... */
10300 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10301 G_OBJECT(compose->focused_editable),
10302 "paste_as_quotation"));
10303 g_object_set_data(G_OBJECT(compose->focused_editable),
10304 "paste_as_quotation",
10305 GINT_TO_POINTER(paste_as_quotation + 1));
10306 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10307 entry_paste_clipboard(compose, compose->focused_editable,
10308 prefs_common.linewrap_pastes,
10309 GDK_SELECTION_CLIPBOARD, NULL);
10310 prefs_common.linewrap_quote = wrap_quote;
10314 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10316 Compose *compose = (Compose *)data;
10317 gint prev_autowrap;
10318 GtkTextBuffer *buffer;
10320 if (compose->focused_editable
10321 #ifndef GENERIC_UMPC
10322 && gtk_widget_has_focus(compose->focused_editable)
10325 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10326 GDK_SELECTION_CLIPBOARD, NULL);
10331 #ifndef GENERIC_UMPC
10332 gtk_widget_has_focus(compose->text) &&
10334 compose->gtkaspell &&
10335 compose->gtkaspell->check_while_typing)
10336 gtkaspell_highlight_all(compose->gtkaspell);
10340 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10342 Compose *compose = (Compose *)data;
10343 gint prev_autowrap;
10344 GtkTextBuffer *buffer;
10346 if (compose->focused_editable
10347 #ifndef GENERIC_UMPC
10348 && gtk_widget_has_focus(compose->focused_editable)
10351 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10352 GDK_SELECTION_CLIPBOARD, NULL);
10357 #ifndef GENERIC_UMPC
10358 gtk_widget_has_focus(compose->text) &&
10360 compose->gtkaspell &&
10361 compose->gtkaspell->check_while_typing)
10362 gtkaspell_highlight_all(compose->gtkaspell);
10366 static void compose_allsel_cb(GtkAction *action, gpointer data)
10368 Compose *compose = (Compose *)data;
10369 if (compose->focused_editable
10370 #ifndef GENERIC_UMPC
10371 && gtk_widget_has_focus(compose->focused_editable)
10374 entry_allsel(compose->focused_editable);
10377 static void textview_move_beginning_of_line (GtkTextView *text)
10379 GtkTextBuffer *buffer;
10383 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10385 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10386 mark = gtk_text_buffer_get_insert(buffer);
10387 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10388 gtk_text_iter_set_line_offset(&ins, 0);
10389 gtk_text_buffer_place_cursor(buffer, &ins);
10392 static void textview_move_forward_character (GtkTextView *text)
10394 GtkTextBuffer *buffer;
10398 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10400 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10401 mark = gtk_text_buffer_get_insert(buffer);
10402 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10403 if (gtk_text_iter_forward_cursor_position(&ins))
10404 gtk_text_buffer_place_cursor(buffer, &ins);
10407 static void textview_move_backward_character (GtkTextView *text)
10409 GtkTextBuffer *buffer;
10413 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10415 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10416 mark = gtk_text_buffer_get_insert(buffer);
10417 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10418 if (gtk_text_iter_backward_cursor_position(&ins))
10419 gtk_text_buffer_place_cursor(buffer, &ins);
10422 static void textview_move_forward_word (GtkTextView *text)
10424 GtkTextBuffer *buffer;
10429 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10431 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10432 mark = gtk_text_buffer_get_insert(buffer);
10433 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10434 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10435 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10436 gtk_text_iter_backward_word_start(&ins);
10437 gtk_text_buffer_place_cursor(buffer, &ins);
10441 static void textview_move_backward_word (GtkTextView *text)
10443 GtkTextBuffer *buffer;
10447 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10449 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10450 mark = gtk_text_buffer_get_insert(buffer);
10451 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10452 if (gtk_text_iter_backward_word_starts(&ins, 1))
10453 gtk_text_buffer_place_cursor(buffer, &ins);
10456 static void textview_move_end_of_line (GtkTextView *text)
10458 GtkTextBuffer *buffer;
10462 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10464 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10465 mark = gtk_text_buffer_get_insert(buffer);
10466 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10467 if (gtk_text_iter_forward_to_line_end(&ins))
10468 gtk_text_buffer_place_cursor(buffer, &ins);
10471 static void textview_move_next_line (GtkTextView *text)
10473 GtkTextBuffer *buffer;
10478 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10480 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10481 mark = gtk_text_buffer_get_insert(buffer);
10482 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10483 offset = gtk_text_iter_get_line_offset(&ins);
10484 if (gtk_text_iter_forward_line(&ins)) {
10485 gtk_text_iter_set_line_offset(&ins, offset);
10486 gtk_text_buffer_place_cursor(buffer, &ins);
10490 static void textview_move_previous_line (GtkTextView *text)
10492 GtkTextBuffer *buffer;
10497 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10499 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10500 mark = gtk_text_buffer_get_insert(buffer);
10501 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10502 offset = gtk_text_iter_get_line_offset(&ins);
10503 if (gtk_text_iter_backward_line(&ins)) {
10504 gtk_text_iter_set_line_offset(&ins, offset);
10505 gtk_text_buffer_place_cursor(buffer, &ins);
10509 static void textview_delete_forward_character (GtkTextView *text)
10511 GtkTextBuffer *buffer;
10513 GtkTextIter ins, end_iter;
10515 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10517 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10518 mark = gtk_text_buffer_get_insert(buffer);
10519 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10521 if (gtk_text_iter_forward_char(&end_iter)) {
10522 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10526 static void textview_delete_backward_character (GtkTextView *text)
10528 GtkTextBuffer *buffer;
10530 GtkTextIter ins, end_iter;
10532 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10534 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10535 mark = gtk_text_buffer_get_insert(buffer);
10536 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10538 if (gtk_text_iter_backward_char(&end_iter)) {
10539 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10543 static void textview_delete_forward_word (GtkTextView *text)
10545 GtkTextBuffer *buffer;
10547 GtkTextIter ins, end_iter;
10549 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10551 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10552 mark = gtk_text_buffer_get_insert(buffer);
10553 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10555 if (gtk_text_iter_forward_word_end(&end_iter)) {
10556 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10560 static void textview_delete_backward_word (GtkTextView *text)
10562 GtkTextBuffer *buffer;
10564 GtkTextIter ins, end_iter;
10566 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10568 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10569 mark = gtk_text_buffer_get_insert(buffer);
10570 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10572 if (gtk_text_iter_backward_word_start(&end_iter)) {
10573 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10577 static void textview_delete_line (GtkTextView *text)
10579 GtkTextBuffer *buffer;
10581 GtkTextIter ins, start_iter, end_iter;
10583 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10585 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10586 mark = gtk_text_buffer_get_insert(buffer);
10587 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10590 gtk_text_iter_set_line_offset(&start_iter, 0);
10593 if (gtk_text_iter_ends_line(&end_iter)){
10594 if (!gtk_text_iter_forward_char(&end_iter))
10595 gtk_text_iter_backward_char(&start_iter);
10598 gtk_text_iter_forward_to_line_end(&end_iter);
10599 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10602 static void textview_delete_to_line_end (GtkTextView *text)
10604 GtkTextBuffer *buffer;
10606 GtkTextIter ins, end_iter;
10608 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10610 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10611 mark = gtk_text_buffer_get_insert(buffer);
10612 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10614 if (gtk_text_iter_ends_line(&end_iter))
10615 gtk_text_iter_forward_char(&end_iter);
10617 gtk_text_iter_forward_to_line_end(&end_iter);
10618 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10621 #define DO_ACTION(name, act) { \
10622 if(!strcmp(name, a_name)) { \
10626 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10628 const gchar *a_name = gtk_action_get_name(action);
10629 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10630 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10631 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10632 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10633 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10634 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10635 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10636 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10637 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10638 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10639 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10640 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10641 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10642 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10646 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10648 Compose *compose = (Compose *)data;
10649 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10650 ComposeCallAdvancedAction action = -1;
10652 action = compose_call_advanced_action_from_path(gaction);
10655 void (*do_action) (GtkTextView *text);
10656 } action_table[] = {
10657 {textview_move_beginning_of_line},
10658 {textview_move_forward_character},
10659 {textview_move_backward_character},
10660 {textview_move_forward_word},
10661 {textview_move_backward_word},
10662 {textview_move_end_of_line},
10663 {textview_move_next_line},
10664 {textview_move_previous_line},
10665 {textview_delete_forward_character},
10666 {textview_delete_backward_character},
10667 {textview_delete_forward_word},
10668 {textview_delete_backward_word},
10669 {textview_delete_line},
10670 {textview_delete_to_line_end}
10673 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10675 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10676 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10677 if (action_table[action].do_action)
10678 action_table[action].do_action(text);
10680 g_warning("Not implemented yet.");
10684 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10686 GtkAllocation allocation;
10690 if (GTK_IS_EDITABLE(widget)) {
10691 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10692 gtk_editable_set_position(GTK_EDITABLE(widget),
10695 if ((parent = gtk_widget_get_parent(widget))
10696 && (parent = gtk_widget_get_parent(parent))
10697 && (parent = gtk_widget_get_parent(parent))) {
10698 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10699 gtk_widget_get_allocation(widget, &allocation);
10700 gint y = allocation.y;
10701 gint height = allocation.height;
10702 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10703 (GTK_SCROLLED_WINDOW(parent));
10705 gfloat value = gtk_adjustment_get_value(shown);
10706 gfloat upper = gtk_adjustment_get_upper(shown);
10707 gfloat page_size = gtk_adjustment_get_page_size(shown);
10708 if (y < (int)value) {
10709 gtk_adjustment_set_value(shown, y - 1);
10711 if ((y + height) > ((int)value + (int)page_size)) {
10712 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10713 gtk_adjustment_set_value(shown,
10714 y + height - (int)page_size - 1);
10716 gtk_adjustment_set_value(shown,
10717 (int)upper - (int)page_size - 1);
10724 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10725 compose->focused_editable = widget;
10727 #ifdef GENERIC_UMPC
10728 if (GTK_IS_TEXT_VIEW(widget)
10729 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10730 g_object_ref(compose->notebook);
10731 g_object_ref(compose->edit_vbox);
10732 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10733 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10734 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10735 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10736 g_object_unref(compose->notebook);
10737 g_object_unref(compose->edit_vbox);
10738 g_signal_handlers_block_by_func(G_OBJECT(widget),
10739 G_CALLBACK(compose_grab_focus_cb),
10741 gtk_widget_grab_focus(widget);
10742 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10743 G_CALLBACK(compose_grab_focus_cb),
10745 } else if (!GTK_IS_TEXT_VIEW(widget)
10746 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10747 g_object_ref(compose->notebook);
10748 g_object_ref(compose->edit_vbox);
10749 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10750 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10751 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10752 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10753 g_object_unref(compose->notebook);
10754 g_object_unref(compose->edit_vbox);
10755 g_signal_handlers_block_by_func(G_OBJECT(widget),
10756 G_CALLBACK(compose_grab_focus_cb),
10758 gtk_widget_grab_focus(widget);
10759 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10760 G_CALLBACK(compose_grab_focus_cb),
10766 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10768 compose->modified = TRUE;
10769 // compose_beautify_paragraph(compose, NULL, TRUE);
10770 #ifndef GENERIC_UMPC
10771 compose_set_title(compose);
10775 static void compose_wrap_cb(GtkAction *action, gpointer data)
10777 Compose *compose = (Compose *)data;
10778 compose_beautify_paragraph(compose, NULL, TRUE);
10781 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10783 Compose *compose = (Compose *)data;
10784 compose_wrap_all_full(compose, TRUE);
10787 static void compose_find_cb(GtkAction *action, gpointer data)
10789 Compose *compose = (Compose *)data;
10791 message_search_compose(compose);
10794 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10797 Compose *compose = (Compose *)data;
10798 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10799 if (compose->autowrap)
10800 compose_wrap_all_full(compose, TRUE);
10801 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10804 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10807 Compose *compose = (Compose *)data;
10808 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10811 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10813 Compose *compose = (Compose *)data;
10815 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10818 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10820 Compose *compose = (Compose *)data;
10822 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10825 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10827 g_free(compose->privacy_system);
10829 compose->privacy_system = g_strdup(account->default_privacy_system);
10830 compose_update_privacy_system_menu_item(compose, warn);
10833 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10835 Compose *compose = (Compose *)data;
10837 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10838 gtk_widget_show(compose->ruler_hbox);
10839 prefs_common.show_ruler = TRUE;
10841 gtk_widget_hide(compose->ruler_hbox);
10842 gtk_widget_queue_resize(compose->edit_vbox);
10843 prefs_common.show_ruler = FALSE;
10847 static void compose_attach_drag_received_cb (GtkWidget *widget,
10848 GdkDragContext *context,
10851 GtkSelectionData *data,
10854 gpointer user_data)
10856 Compose *compose = (Compose *)user_data;
10860 type = gtk_selection_data_get_data_type(data);
10861 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10863 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10865 ) && gtk_drag_get_source_widget(context) !=
10866 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10867 list = uri_list_extract_filenames(
10868 (const gchar *)gtk_selection_data_get_data(data));
10869 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10870 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10871 compose_attach_append
10872 (compose, (const gchar *)tmp->data,
10873 utf8_filename, NULL, NULL);
10874 g_free(utf8_filename);
10876 if (list) compose_changed_cb(NULL, compose);
10877 list_free_strings(list);
10879 } else if (gtk_drag_get_source_widget(context)
10880 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10881 /* comes from our summaryview */
10882 SummaryView * summaryview = NULL;
10883 GSList * list = NULL, *cur = NULL;
10885 if (mainwindow_get_mainwindow())
10886 summaryview = mainwindow_get_mainwindow()->summaryview;
10889 list = summary_get_selected_msg_list(summaryview);
10891 for (cur = list; cur; cur = cur->next) {
10892 MsgInfo *msginfo = (MsgInfo *)cur->data;
10893 gchar *file = NULL;
10895 file = procmsg_get_message_file_full(msginfo,
10898 compose_attach_append(compose, (const gchar *)file,
10899 (const gchar *)file, "message/rfc822", NULL);
10903 g_slist_free(list);
10907 static gboolean compose_drag_drop(GtkWidget *widget,
10908 GdkDragContext *drag_context,
10910 guint time, gpointer user_data)
10912 /* not handling this signal makes compose_insert_drag_received_cb
10917 static gboolean completion_set_focus_to_subject
10918 (GtkWidget *widget,
10919 GdkEventKey *event,
10922 cm_return_val_if_fail(compose != NULL, FALSE);
10924 /* make backtab move to subject field */
10925 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10926 gtk_widget_grab_focus(compose->subject_entry);
10932 static void compose_insert_drag_received_cb (GtkWidget *widget,
10933 GdkDragContext *drag_context,
10936 GtkSelectionData *data,
10939 gpointer user_data)
10941 Compose *compose = (Compose *)user_data;
10945 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10947 type = gtk_selection_data_get_data_type(data);
10949 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10951 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10953 AlertValue val = G_ALERTDEFAULT;
10954 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10956 list = uri_list_extract_filenames(ddata);
10957 if (list == NULL && strstr(ddata, "://")) {
10958 /* Assume a list of no files, and data has ://, is a remote link */
10959 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10960 gchar *tmpfile = get_tmp_file();
10961 str_write_to_file(tmpdata, tmpfile);
10963 compose_insert_file(compose, tmpfile);
10964 claws_unlink(tmpfile);
10966 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10967 compose_beautify_paragraph(compose, NULL, TRUE);
10970 switch (prefs_common.compose_dnd_mode) {
10971 case COMPOSE_DND_ASK:
10972 val = alertpanel_full(_("Insert or attach?"),
10973 _("Do you want to insert the contents of the file(s) "
10974 "into the message body, or attach it to the email?"),
10975 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10976 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10978 case COMPOSE_DND_INSERT:
10979 val = G_ALERTALTERNATE;
10981 case COMPOSE_DND_ATTACH:
10982 val = G_ALERTOTHER;
10985 /* unexpected case */
10986 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10989 if (val & G_ALERTDISABLE) {
10990 val &= ~G_ALERTDISABLE;
10991 /* remember what action to perform by default, only if we don't click Cancel */
10992 if (val == G_ALERTALTERNATE)
10993 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10994 else if (val == G_ALERTOTHER)
10995 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10998 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10999 gtk_drag_finish(drag_context, FALSE, FALSE, time);
11000 list_free_strings(list);
11003 } else if (val == G_ALERTOTHER) {
11004 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
11005 list_free_strings(list);
11010 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11011 compose_insert_file(compose, (const gchar *)tmp->data);
11013 list_free_strings(list);
11015 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11020 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11023 static void compose_header_drag_received_cb (GtkWidget *widget,
11024 GdkDragContext *drag_context,
11027 GtkSelectionData *data,
11030 gpointer user_data)
11032 GtkEditable *entry = (GtkEditable *)user_data;
11033 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11035 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11038 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11039 gchar *decoded=g_new(gchar, strlen(email));
11042 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11043 gtk_editable_delete_text(entry, 0, -1);
11044 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11045 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11049 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11052 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11054 Compose *compose = (Compose *)data;
11056 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11057 compose->return_receipt = TRUE;
11059 compose->return_receipt = FALSE;
11062 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11064 Compose *compose = (Compose *)data;
11066 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11067 compose->remove_references = TRUE;
11069 compose->remove_references = FALSE;
11072 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11073 ComposeHeaderEntry *headerentry)
11075 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11079 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11080 GdkEventKey *event,
11081 ComposeHeaderEntry *headerentry)
11083 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11084 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11085 !(event->state & GDK_MODIFIER_MASK) &&
11086 (event->keyval == GDK_KEY_BackSpace) &&
11087 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11088 gtk_container_remove
11089 (GTK_CONTAINER(headerentry->compose->header_table),
11090 headerentry->combo);
11091 gtk_container_remove
11092 (GTK_CONTAINER(headerentry->compose->header_table),
11093 headerentry->entry);
11094 headerentry->compose->header_list =
11095 g_slist_remove(headerentry->compose->header_list,
11097 g_free(headerentry);
11098 } else if (event->keyval == GDK_KEY_Tab) {
11099 if (headerentry->compose->header_last == headerentry) {
11100 /* Override default next focus, and give it to subject_entry
11101 * instead of notebook tabs
11103 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11104 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11111 static gboolean scroll_postpone(gpointer data)
11113 Compose *compose = (Compose *)data;
11115 cm_return_val_if_fail(!compose->batch, FALSE);
11117 GTK_EVENTS_FLUSH();
11118 compose_show_first_last_header(compose, FALSE);
11122 static void compose_headerentry_changed_cb(GtkWidget *entry,
11123 ComposeHeaderEntry *headerentry)
11125 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11126 compose_create_header_entry(headerentry->compose);
11127 g_signal_handlers_disconnect_matched
11128 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11129 0, 0, NULL, NULL, headerentry);
11131 if (!headerentry->compose->batch)
11132 g_timeout_add(0, scroll_postpone, headerentry->compose);
11136 static gboolean compose_defer_auto_save_draft(Compose *compose)
11138 compose->draft_timeout_tag = -1;
11139 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11143 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11145 GtkAdjustment *vadj;
11147 cm_return_if_fail(compose);
11148 cm_return_if_fail(!compose->batch);
11149 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11150 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11151 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11152 gtk_widget_get_parent(compose->header_table)));
11153 gtk_adjustment_set_value(vadj, (show_first ?
11154 gtk_adjustment_get_lower(vadj) :
11155 (gtk_adjustment_get_upper(vadj) -
11156 gtk_adjustment_get_page_size(vadj))));
11157 gtk_adjustment_changed(vadj);
11160 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11161 const gchar *text, gint len, Compose *compose)
11163 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11164 (G_OBJECT(compose->text), "paste_as_quotation"));
11167 cm_return_if_fail(text != NULL);
11169 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11170 G_CALLBACK(text_inserted),
11172 if (paste_as_quotation) {
11174 const gchar *qmark;
11176 GtkTextIter start_iter;
11179 len = strlen(text);
11181 new_text = g_strndup(text, len);
11183 qmark = compose_quote_char_from_context(compose);
11185 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11186 gtk_text_buffer_place_cursor(buffer, iter);
11188 pos = gtk_text_iter_get_offset(iter);
11190 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11191 _("Quote format error at line %d."));
11192 quote_fmt_reset_vartable();
11194 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11195 GINT_TO_POINTER(paste_as_quotation - 1));
11197 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11198 gtk_text_buffer_place_cursor(buffer, iter);
11199 gtk_text_buffer_delete_mark(buffer, mark);
11201 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11202 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11203 compose_beautify_paragraph(compose, &start_iter, FALSE);
11204 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11205 gtk_text_buffer_delete_mark(buffer, mark);
11207 if (strcmp(text, "\n") || compose->automatic_break
11208 || gtk_text_iter_starts_line(iter)) {
11209 GtkTextIter before_ins;
11210 gtk_text_buffer_insert(buffer, iter, text, len);
11211 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11212 before_ins = *iter;
11213 gtk_text_iter_backward_chars(&before_ins, len);
11214 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11217 /* check if the preceding is just whitespace or quote */
11218 GtkTextIter start_line;
11219 gchar *tmp = NULL, *quote = NULL;
11220 gint quote_len = 0, is_normal = 0;
11221 start_line = *iter;
11222 gtk_text_iter_set_line_offset(&start_line, 0);
11223 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11226 if (*tmp == '\0') {
11229 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11237 gtk_text_buffer_insert(buffer, iter, text, len);
11239 gtk_text_buffer_insert_with_tags_by_name(buffer,
11240 iter, text, len, "no_join", NULL);
11245 if (!paste_as_quotation) {
11246 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11247 compose_beautify_paragraph(compose, iter, FALSE);
11248 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11249 gtk_text_buffer_delete_mark(buffer, mark);
11252 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11253 G_CALLBACK(text_inserted),
11255 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11257 if (prefs_common.autosave &&
11258 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11259 compose->draft_timeout_tag != -2 /* disabled while loading */)
11260 compose->draft_timeout_tag = g_timeout_add
11261 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11265 static void compose_check_all(GtkAction *action, gpointer data)
11267 Compose *compose = (Compose *)data;
11268 if (!compose->gtkaspell)
11271 if (gtk_widget_has_focus(compose->subject_entry))
11272 claws_spell_entry_check_all(
11273 CLAWS_SPELL_ENTRY(compose->subject_entry));
11275 gtkaspell_check_all(compose->gtkaspell);
11278 static void compose_highlight_all(GtkAction *action, gpointer data)
11280 Compose *compose = (Compose *)data;
11281 if (compose->gtkaspell) {
11282 claws_spell_entry_recheck_all(
11283 CLAWS_SPELL_ENTRY(compose->subject_entry));
11284 gtkaspell_highlight_all(compose->gtkaspell);
11288 static void compose_check_backwards(GtkAction *action, gpointer data)
11290 Compose *compose = (Compose *)data;
11291 if (!compose->gtkaspell) {
11292 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11296 if (gtk_widget_has_focus(compose->subject_entry))
11297 claws_spell_entry_check_backwards(
11298 CLAWS_SPELL_ENTRY(compose->subject_entry));
11300 gtkaspell_check_backwards(compose->gtkaspell);
11303 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11305 Compose *compose = (Compose *)data;
11306 if (!compose->gtkaspell) {
11307 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11311 if (gtk_widget_has_focus(compose->subject_entry))
11312 claws_spell_entry_check_forwards_go(
11313 CLAWS_SPELL_ENTRY(compose->subject_entry));
11315 gtkaspell_check_forwards_go(compose->gtkaspell);
11320 *\brief Guess originating forward account from MsgInfo and several
11321 * "common preference" settings. Return NULL if no guess.
11323 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11325 PrefsAccount *account = NULL;
11327 cm_return_val_if_fail(msginfo, NULL);
11328 cm_return_val_if_fail(msginfo->folder, NULL);
11329 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11331 if (msginfo->folder->prefs->enable_default_account)
11332 account = account_find_from_id(msginfo->folder->prefs->default_account);
11335 account = msginfo->folder->folder->account;
11337 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11339 Xstrdup_a(to, msginfo->to, return NULL);
11340 extract_address(to);
11341 account = account_find_from_address(to, FALSE);
11344 if (!account && prefs_common.forward_account_autosel) {
11345 gchar cc[BUFFSIZE];
11346 if (!procheader_get_header_from_msginfo
11347 (msginfo, cc,sizeof cc , "Cc:")) {
11348 gchar *buf = cc + strlen("Cc:");
11349 extract_address(buf);
11350 account = account_find_from_address(buf, FALSE);
11354 if (!account && prefs_common.forward_account_autosel) {
11355 gchar deliveredto[BUFFSIZE];
11356 if (!procheader_get_header_from_msginfo
11357 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11358 gchar *buf = deliveredto + strlen("Delivered-To:");
11359 extract_address(buf);
11360 account = account_find_from_address(buf, FALSE);
11367 gboolean compose_close(Compose *compose)
11371 if (!g_mutex_trylock(compose->mutex)) {
11372 /* we have to wait for the (possibly deferred by auto-save)
11373 * drafting to be done, before destroying the compose under
11375 debug_print("waiting for drafting to finish...\n");
11376 compose_allow_user_actions(compose, FALSE);
11377 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11380 cm_return_val_if_fail(compose, FALSE);
11381 gtkut_widget_get_uposition(compose->window, &x, &y);
11382 if (!compose->batch) {
11383 prefs_common.compose_x = x;
11384 prefs_common.compose_y = y;
11386 g_mutex_unlock(compose->mutex);
11387 compose_destroy(compose);
11392 * Add entry field for each address in list.
11393 * \param compose E-Mail composition object.
11394 * \param listAddress List of (formatted) E-Mail addresses.
11396 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11399 node = listAddress;
11401 addr = ( gchar * ) node->data;
11402 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11403 node = g_list_next( node );
11407 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11408 guint action, gboolean opening_multiple)
11410 gchar *body = NULL;
11411 GSList *new_msglist = NULL;
11412 MsgInfo *tmp_msginfo = NULL;
11413 gboolean originally_enc = FALSE;
11414 gboolean originally_sig = FALSE;
11415 Compose *compose = NULL;
11416 gchar *s_system = NULL;
11418 cm_return_if_fail(msgview != NULL);
11420 cm_return_if_fail(msginfo_list != NULL);
11422 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11423 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11424 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11426 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11427 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11428 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11429 orig_msginfo, mimeinfo);
11430 if (tmp_msginfo != NULL) {
11431 new_msglist = g_slist_append(NULL, tmp_msginfo);
11433 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11434 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11435 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11437 tmp_msginfo->folder = orig_msginfo->folder;
11438 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11439 if (orig_msginfo->tags) {
11440 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11441 tmp_msginfo->folder->tags_dirty = TRUE;
11447 if (!opening_multiple)
11448 body = messageview_get_selection(msgview);
11451 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11452 procmsg_msginfo_free(tmp_msginfo);
11453 g_slist_free(new_msglist);
11455 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11457 if (compose && originally_enc) {
11458 compose_force_encryption(compose, compose->account, FALSE, s_system);
11461 if (compose && originally_sig && compose->account->default_sign_reply) {
11462 compose_force_signing(compose, compose->account, s_system);
11466 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11469 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11472 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11473 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11474 GSList *cur = msginfo_list;
11475 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11476 "messages. Opening the windows "
11477 "could take some time. Do you "
11478 "want to continue?"),
11479 g_slist_length(msginfo_list));
11480 if (g_slist_length(msginfo_list) > 9
11481 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11482 != G_ALERTALTERNATE) {
11487 /* We'll open multiple compose windows */
11488 /* let the WM place the next windows */
11489 compose_force_window_origin = FALSE;
11490 for (; cur; cur = cur->next) {
11492 tmplist.data = cur->data;
11493 tmplist.next = NULL;
11494 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11496 compose_force_window_origin = TRUE;
11498 /* forwarding multiple mails as attachments is done via a
11499 * single compose window */
11500 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11504 void compose_check_for_email_account(Compose *compose)
11506 PrefsAccount *ac = NULL, *curr = NULL;
11512 if (compose->account && compose->account->protocol == A_NNTP) {
11513 ac = account_get_cur_account();
11514 if (ac->protocol == A_NNTP) {
11515 list = account_get_list();
11517 for( ; list != NULL ; list = g_list_next(list)) {
11518 curr = (PrefsAccount *) list->data;
11519 if (curr->protocol != A_NNTP) {
11525 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11530 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11531 const gchar *address)
11533 GSList *msginfo_list = NULL;
11534 gchar *body = messageview_get_selection(msgview);
11537 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11539 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11540 compose_check_for_email_account(compose);
11541 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11542 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11543 compose_reply_set_subject(compose, msginfo);
11546 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11549 void compose_set_position(Compose *compose, gint pos)
11551 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11553 gtkut_text_view_set_position(text, pos);
11556 gboolean compose_search_string(Compose *compose,
11557 const gchar *str, gboolean case_sens)
11559 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11561 return gtkut_text_view_search_string(text, str, case_sens);
11564 gboolean compose_search_string_backward(Compose *compose,
11565 const gchar *str, gboolean case_sens)
11567 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11569 return gtkut_text_view_search_string_backward(text, str, case_sens);
11572 /* allocate a msginfo structure and populate its data from a compose data structure */
11573 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11575 MsgInfo *newmsginfo;
11577 gchar buf[BUFFSIZE];
11579 cm_return_val_if_fail( compose != NULL, NULL );
11581 newmsginfo = procmsg_msginfo_new();
11584 get_rfc822_date(buf, sizeof(buf));
11585 newmsginfo->date = g_strdup(buf);
11588 if (compose->from_name) {
11589 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11590 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11594 if (compose->subject_entry)
11595 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11597 /* to, cc, reply-to, newsgroups */
11598 for (list = compose->header_list; list; list = list->next) {
11599 gchar *header = gtk_editable_get_chars(
11601 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11602 gchar *entry = gtk_editable_get_chars(
11603 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11605 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11606 if ( newmsginfo->to == NULL ) {
11607 newmsginfo->to = g_strdup(entry);
11608 } else if (entry && *entry) {
11609 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11610 g_free(newmsginfo->to);
11611 newmsginfo->to = tmp;
11614 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11615 if ( newmsginfo->cc == NULL ) {
11616 newmsginfo->cc = g_strdup(entry);
11617 } else if (entry && *entry) {
11618 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11619 g_free(newmsginfo->cc);
11620 newmsginfo->cc = tmp;
11623 if ( strcasecmp(header,
11624 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11625 if ( newmsginfo->newsgroups == NULL ) {
11626 newmsginfo->newsgroups = g_strdup(entry);
11627 } else if (entry && *entry) {
11628 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11629 g_free(newmsginfo->newsgroups);
11630 newmsginfo->newsgroups = tmp;
11638 /* other data is unset */
11644 /* update compose's dictionaries from folder dict settings */
11645 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11646 FolderItem *folder_item)
11648 cm_return_if_fail(compose != NULL);
11650 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11651 FolderItemPrefs *prefs = folder_item->prefs;
11653 if (prefs->enable_default_dictionary)
11654 gtkaspell_change_dict(compose->gtkaspell,
11655 prefs->default_dictionary, FALSE);
11656 if (folder_item->prefs->enable_default_alt_dictionary)
11657 gtkaspell_change_alt_dict(compose->gtkaspell,
11658 prefs->default_alt_dictionary);
11659 if (prefs->enable_default_dictionary
11660 || prefs->enable_default_alt_dictionary)
11661 compose_spell_menu_changed(compose);
11666 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11668 Compose *compose = (Compose *)data;
11670 cm_return_if_fail(compose != NULL);
11672 gtk_widget_grab_focus(compose->text);