2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
397 static void compose_close_cb (GtkAction *action,
399 static void compose_print_cb (GtkAction *action,
402 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
404 static void compose_address_cb (GtkAction *action,
406 static void about_show_cb (GtkAction *action,
408 static void compose_template_activate_cb(GtkWidget *widget,
411 static void compose_ext_editor_cb (GtkAction *action,
414 static gint compose_delete_cb (GtkWidget *widget,
418 static void compose_undo_cb (GtkAction *action,
420 static void compose_redo_cb (GtkAction *action,
422 static void compose_cut_cb (GtkAction *action,
424 static void compose_copy_cb (GtkAction *action,
426 static void compose_paste_cb (GtkAction *action,
428 static void compose_paste_as_quote_cb (GtkAction *action,
430 static void compose_paste_no_wrap_cb (GtkAction *action,
432 static void compose_paste_wrap_cb (GtkAction *action,
434 static void compose_allsel_cb (GtkAction *action,
437 static void compose_advanced_action_cb (GtkAction *action,
440 static void compose_grab_focus_cb (GtkWidget *widget,
443 static void compose_changed_cb (GtkTextBuffer *textbuf,
446 static void compose_wrap_cb (GtkAction *action,
448 static void compose_wrap_all_cb (GtkAction *action,
450 static void compose_find_cb (GtkAction *action,
452 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
454 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
457 static void compose_toggle_ruler_cb (GtkToggleAction *action,
459 static void compose_toggle_sign_cb (GtkToggleAction *action,
461 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
463 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
472 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
474 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
475 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
476 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
478 static void compose_attach_drag_received_cb (GtkWidget *widget,
479 GdkDragContext *drag_context,
482 GtkSelectionData *data,
486 static void compose_insert_drag_received_cb (GtkWidget *widget,
487 GdkDragContext *drag_context,
490 GtkSelectionData *data,
494 static void compose_header_drag_received_cb (GtkWidget *widget,
495 GdkDragContext *drag_context,
498 GtkSelectionData *data,
503 static gboolean compose_drag_drop (GtkWidget *widget,
504 GdkDragContext *drag_context,
506 guint time, gpointer user_data);
507 static gboolean completion_set_focus_to_subject
512 static void text_inserted (GtkTextBuffer *buffer,
517 static Compose *compose_generic_reply(MsgInfo *msginfo,
518 ComposeQuoteMode quote_mode,
522 gboolean followup_and_reply_to,
525 static void compose_headerentry_changed_cb (GtkWidget *entry,
526 ComposeHeaderEntry *headerentry);
527 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
529 ComposeHeaderEntry *headerentry);
530 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
531 ComposeHeaderEntry *headerentry);
533 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
535 static void compose_allow_user_actions (Compose *compose, gboolean allow);
537 static void compose_nothing_cb (GtkAction *action, gpointer data)
543 static void compose_check_all (GtkAction *action, gpointer data);
544 static void compose_highlight_all (GtkAction *action, gpointer data);
545 static void compose_check_backwards (GtkAction *action, gpointer data);
546 static void compose_check_forwards_go (GtkAction *action, gpointer data);
549 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
551 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
554 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
555 FolderItem *folder_item);
557 static void compose_attach_update_label(Compose *compose);
558 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
559 gboolean respect_default_to);
560 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data);
562 static GtkActionEntry compose_popup_entries[] =
564 {"Compose", NULL, "Compose" },
565 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
566 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
567 {"Compose/---", NULL, "---", NULL, NULL, NULL },
568 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
571 static GtkActionEntry compose_entries[] =
573 {"Menu", NULL, "Menu" },
575 {"Message", NULL, N_("_Message") },
576 {"Edit", NULL, N_("_Edit") },
578 {"Spelling", NULL, N_("_Spelling") },
580 {"Options", NULL, N_("_Options") },
581 {"Tools", NULL, N_("_Tools") },
582 {"Help", NULL, N_("_Help") },
584 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
585 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
586 {"Message/---", NULL, "---" },
588 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
589 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
590 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
591 /* {"Message/---", NULL, "---" }, */
592 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
593 /* {"Message/---", NULL, "---" }, */
594 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
595 /* {"Message/---", NULL, "---" }, */
596 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
599 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
600 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
601 {"Edit/---", NULL, "---" },
603 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
604 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
605 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
607 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
608 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
609 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
610 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
612 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
614 {"Edit/Advanced", NULL, N_("A_dvanced") },
615 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
616 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
617 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
618 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
619 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
620 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
621 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
622 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
623 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
624 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
625 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
626 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
627 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
628 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
630 /* {"Edit/---", NULL, "---" }, */
631 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
633 /* {"Edit/---", NULL, "---" }, */
634 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
635 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
636 /* {"Edit/---", NULL, "---" }, */
637 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
640 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
641 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
642 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
643 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
645 {"Spelling/---", NULL, "---" },
646 {"Spelling/Options", NULL, N_("_Options") },
651 {"Options/ReplyMode", NULL, N_("Reply _mode") },
652 {"Options/---", NULL, "---" },
653 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
654 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
656 /* {"Options/---", NULL, "---" }, */
658 {"Options/Priority", NULL, N_("_Priority") },
660 {"Options/Encoding", NULL, N_("Character _encoding") },
661 {"Options/Encoding/---", NULL, "---" },
662 #define ENC_ACTION(cs_char,c_char,string) \
663 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
665 {"Options/Encoding/Western", NULL, N_("Western European") },
666 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
667 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
668 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
669 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
670 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
671 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
672 {"Options/Encoding/Korean", NULL, N_("Korean") },
673 {"Options/Encoding/Thai", NULL, N_("Thai") },
676 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
678 {"Tools/Template", NULL, N_("_Template") },
679 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
680 {"Tools/Actions", NULL, N_("Actio_ns") },
681 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
684 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
687 static GtkToggleActionEntry compose_toggle_entries[] =
689 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
690 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
691 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
692 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
693 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
694 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
695 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
698 static GtkRadioActionEntry compose_radio_rm_entries[] =
700 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
701 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
702 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
703 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
706 static GtkRadioActionEntry compose_radio_prio_entries[] =
708 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
709 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
710 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
711 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
712 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
715 static GtkRadioActionEntry compose_radio_enc_entries[] =
717 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
751 static GtkTargetEntry compose_mime_types[] =
753 {"text/uri-list", 0, 0},
754 {"UTF8_STRING", 0, 0},
758 static gboolean compose_put_existing_to_front(MsgInfo *info)
760 GList *compose_list = compose_get_compose_list();
764 for (elem = compose_list; elem != NULL && elem->data != NULL;
766 Compose *c = (Compose*)elem->data;
768 if (!c->targetinfo || !c->targetinfo->msgid ||
772 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
773 gtkut_window_popup(c->window);
781 static GdkColor quote_color1 =
782 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
783 static GdkColor quote_color2 =
784 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
785 static GdkColor quote_color3 =
786 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
788 static GdkColor quote_bgcolor1 =
789 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 static GdkColor quote_bgcolor2 =
791 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
792 static GdkColor quote_bgcolor3 =
793 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
795 static GdkColor signature_color = {
802 static GdkColor uri_color = {
809 static void compose_create_tags(GtkTextView *text, Compose *compose)
811 GtkTextBuffer *buffer;
812 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
813 #if !GTK_CHECK_VERSION(2, 24, 0)
820 buffer = gtk_text_view_get_buffer(text);
822 if (prefs_common.enable_color) {
823 /* grab the quote colors, converting from an int to a GdkColor */
824 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
826 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
828 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
830 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
832 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
834 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
836 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
838 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
841 signature_color = quote_color1 = quote_color2 = quote_color3 =
842 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
845 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
846 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
847 "foreground-gdk", "e_color1,
848 "paragraph-background-gdk", "e_bgcolor1,
850 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
851 "foreground-gdk", "e_color2,
852 "paragraph-background-gdk", "e_bgcolor2,
854 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
855 "foreground-gdk", "e_color3,
856 "paragraph-background-gdk", "e_bgcolor3,
859 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
860 "foreground-gdk", "e_color1,
862 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
863 "foreground-gdk", "e_color2,
865 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
866 "foreground-gdk", "e_color3,
870 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
871 "foreground-gdk", &signature_color,
874 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
875 "foreground-gdk", &uri_color,
877 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
878 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
880 #if !GTK_CHECK_VERSION(2, 24, 0)
881 color[0] = quote_color1;
882 color[1] = quote_color2;
883 color[2] = quote_color3;
884 color[3] = quote_bgcolor1;
885 color[4] = quote_bgcolor2;
886 color[5] = quote_bgcolor3;
887 color[6] = signature_color;
888 color[7] = uri_color;
890 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
891 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
893 for (i = 0; i < 8; i++) {
894 if (success[i] == FALSE) {
895 g_warning("Compose: color allocation failed.\n");
896 quote_color1 = quote_color2 = quote_color3 =
897 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
898 signature_color = uri_color = black;
904 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
907 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
910 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
912 return compose_generic_new(account, mailto, item, NULL, NULL);
915 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
917 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
920 #define SCROLL_TO_CURSOR(compose) { \
921 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
922 gtk_text_view_get_buffer( \
923 GTK_TEXT_VIEW(compose->text))); \
924 gtk_text_view_scroll_mark_onscreen( \
925 GTK_TEXT_VIEW(compose->text), \
929 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
932 if (folderidentifier) {
933 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
934 prefs_common.compose_save_to_history = add_history(
935 prefs_common.compose_save_to_history, folderidentifier);
936 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
937 prefs_common.compose_save_to_history);
940 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
941 if (folderidentifier)
942 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
944 gtk_entry_set_text(GTK_ENTRY(entry), "");
947 static gchar *compose_get_save_to(Compose *compose)
950 gchar *result = NULL;
951 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
952 result = gtk_editable_get_chars(entry, 0, -1);
955 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
956 prefs_common.compose_save_to_history = add_history(
957 prefs_common.compose_save_to_history, result);
958 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
959 prefs_common.compose_save_to_history);
964 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
965 GList *attach_files, GList *listAddress )
968 GtkTextView *textview;
969 GtkTextBuffer *textbuf;
971 const gchar *subject_format = NULL;
972 const gchar *body_format = NULL;
973 gchar *mailto_from = NULL;
974 PrefsAccount *mailto_account = NULL;
975 MsgInfo* dummyinfo = NULL;
976 gint cursor_pos = -1;
977 MailField mfield = NO_FIELD_PRESENT;
981 /* check if mailto defines a from */
982 if (mailto && *mailto != '\0') {
983 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
984 /* mailto defines a from, check if we can get account prefs from it,
985 if not, the account prefs will be guessed using other ways, but we'll keep
988 mailto_account = account_find_from_address(mailto_from, TRUE);
990 account = mailto_account;
993 /* if no account prefs set from mailto, set if from folder prefs (if any) */
994 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
995 account = account_find_from_id(item->prefs->default_account);
997 /* if no account prefs set, fallback to the current one */
998 if (!account) account = cur_account;
999 cm_return_val_if_fail(account != NULL, NULL);
1001 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1003 /* override from name if mailto asked for it */
1005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1006 g_free(mailto_from);
1008 /* override from name according to folder properties */
1009 if (item && item->prefs &&
1010 item->prefs->compose_with_format &&
1011 item->prefs->compose_override_from_format &&
1012 *item->prefs->compose_override_from_format != '\0') {
1017 dummyinfo = compose_msginfo_new_from_compose(compose);
1019 /* decode \-escape sequences in the internal representation of the quote format */
1020 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1021 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1024 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1025 compose->gtkaspell);
1027 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1029 quote_fmt_scan_string(tmp);
1032 buf = quote_fmt_get_buffer();
1034 alertpanel_error(_("New message From format error."));
1036 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1037 quote_fmt_reset_vartable();
1042 compose->replyinfo = NULL;
1043 compose->fwdinfo = NULL;
1045 textview = GTK_TEXT_VIEW(compose->text);
1046 textbuf = gtk_text_view_get_buffer(textview);
1047 compose_create_tags(textview, compose);
1049 undo_block(compose->undostruct);
1051 compose_set_dictionaries_from_folder_prefs(compose, item);
1054 if (account->auto_sig)
1055 compose_insert_sig(compose, FALSE);
1056 gtk_text_buffer_get_start_iter(textbuf, &iter);
1057 gtk_text_buffer_place_cursor(textbuf, &iter);
1059 if (account->protocol != A_NNTP) {
1060 if (mailto && *mailto != '\0') {
1061 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1064 compose_set_folder_prefs(compose, item, TRUE);
1066 if (item && item->ret_rcpt) {
1067 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1070 if (mailto && *mailto != '\0') {
1071 if (!strchr(mailto, '@'))
1072 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1074 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1075 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1076 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1077 mfield = TO_FIELD_PRESENT;
1080 * CLAWS: just don't allow return receipt request, even if the user
1081 * may want to send an email. simple but foolproof.
1083 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1085 compose_add_field_list( compose, listAddress );
1087 if (item && item->prefs && item->prefs->compose_with_format) {
1088 subject_format = item->prefs->compose_subject_format;
1089 body_format = item->prefs->compose_body_format;
1090 } else if (account->compose_with_format) {
1091 subject_format = account->compose_subject_format;
1092 body_format = account->compose_body_format;
1093 } else if (prefs_common.compose_with_format) {
1094 subject_format = prefs_common.compose_subject_format;
1095 body_format = prefs_common.compose_body_format;
1098 if (subject_format || body_format) {
1101 && *subject_format != '\0' )
1103 gchar *subject = NULL;
1108 dummyinfo = compose_msginfo_new_from_compose(compose);
1110 /* decode \-escape sequences in the internal representation of the quote format */
1111 tmp = g_malloc(strlen(subject_format)+1);
1112 pref_get_unescaped_pref(tmp, subject_format);
1114 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1116 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1117 compose->gtkaspell);
1119 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1121 quote_fmt_scan_string(tmp);
1124 buf = quote_fmt_get_buffer();
1126 alertpanel_error(_("New message subject format error."));
1128 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1129 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1130 quote_fmt_reset_vartable();
1134 mfield = SUBJECT_FIELD_PRESENT;
1138 && *body_format != '\0' )
1141 GtkTextBuffer *buffer;
1142 GtkTextIter start, end;
1146 dummyinfo = compose_msginfo_new_from_compose(compose);
1148 text = GTK_TEXT_VIEW(compose->text);
1149 buffer = gtk_text_view_get_buffer(text);
1150 gtk_text_buffer_get_start_iter(buffer, &start);
1151 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1152 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1154 compose_quote_fmt(compose, dummyinfo,
1156 NULL, tmp, FALSE, TRUE,
1157 _("The body of the \"New message\" template has an error at line %d."));
1158 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1159 quote_fmt_reset_vartable();
1163 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1164 gtkaspell_highlight_all(compose->gtkaspell);
1166 mfield = BODY_FIELD_PRESENT;
1170 procmsg_msginfo_free( dummyinfo );
1176 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1177 ainfo = (AttachInfo *) curr->data;
1178 compose_attach_append(compose, ainfo->file, ainfo->name,
1179 ainfo->content_type, ainfo->charset);
1183 compose_show_first_last_header(compose, TRUE);
1185 /* Set save folder */
1186 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1187 gchar *folderidentifier;
1189 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1190 folderidentifier = folder_item_get_identifier(item);
1191 compose_set_save_to(compose, folderidentifier);
1192 g_free(folderidentifier);
1195 /* Place cursor according to provided input (mfield) */
1197 case NO_FIELD_PRESENT:
1198 if (compose->header_last)
1199 gtk_widget_grab_focus(compose->header_last->entry);
1201 case TO_FIELD_PRESENT:
1202 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1204 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1207 gtk_widget_grab_focus(compose->subject_entry);
1209 case SUBJECT_FIELD_PRESENT:
1210 textview = GTK_TEXT_VIEW(compose->text);
1213 textbuf = gtk_text_view_get_buffer(textview);
1216 mark = gtk_text_buffer_get_insert(textbuf);
1217 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1218 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1220 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1221 * only defers where it comes to the variable body
1222 * is not null. If no body is present compose->text
1223 * will be null in which case you cannot place the
1224 * cursor inside the component so. An empty component
1225 * is therefore created before placing the cursor
1227 case BODY_FIELD_PRESENT:
1228 cursor_pos = quote_fmt_get_cursor_pos();
1229 if (cursor_pos == -1)
1230 gtk_widget_grab_focus(compose->header_last->entry);
1232 gtk_widget_grab_focus(compose->text);
1236 undo_unblock(compose->undostruct);
1238 if (prefs_common.auto_exteditor)
1239 compose_exec_ext_editor(compose);
1241 compose->draft_timeout_tag = -1;
1242 SCROLL_TO_CURSOR(compose);
1244 compose->modified = FALSE;
1245 compose_set_title(compose);
1247 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1252 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1253 gboolean override_pref, const gchar *system)
1255 const gchar *privacy = NULL;
1257 cm_return_if_fail(compose != NULL);
1258 cm_return_if_fail(account != NULL);
1260 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1265 else if (account->default_privacy_system
1266 && strlen(account->default_privacy_system)) {
1267 privacy = account->default_privacy_system;
1269 GSList *privacy_avail = privacy_get_system_ids();
1270 if (privacy_avail && g_slist_length(privacy_avail)) {
1271 privacy = (gchar *)(privacy_avail->data);
1274 if (privacy != NULL) {
1276 g_free(compose->privacy_system);
1277 compose->privacy_system = NULL;
1279 if (compose->privacy_system == NULL)
1280 compose->privacy_system = g_strdup(privacy);
1281 else if (*(compose->privacy_system) == '\0') {
1282 g_free(compose->privacy_system);
1283 compose->privacy_system = g_strdup(privacy);
1285 compose_update_privacy_system_menu_item(compose, FALSE);
1286 compose_use_encryption(compose, TRUE);
1290 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1292 const gchar *privacy = NULL;
1296 else if (account->default_privacy_system
1297 && strlen(account->default_privacy_system)) {
1298 privacy = account->default_privacy_system;
1300 GSList *privacy_avail = privacy_get_system_ids();
1301 if (privacy_avail && g_slist_length(privacy_avail)) {
1302 privacy = (gchar *)(privacy_avail->data);
1306 if (privacy != NULL) {
1308 g_free(compose->privacy_system);
1309 compose->privacy_system = NULL;
1311 if (compose->privacy_system == NULL)
1312 compose->privacy_system = g_strdup(privacy);
1313 compose_update_privacy_system_menu_item(compose, FALSE);
1314 compose_use_signing(compose, TRUE);
1318 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1322 Compose *compose = NULL;
1324 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1326 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1327 cm_return_val_if_fail(msginfo != NULL, NULL);
1329 list_len = g_slist_length(msginfo_list);
1333 case COMPOSE_REPLY_TO_ADDRESS:
1334 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1335 FALSE, prefs_common.default_reply_list, FALSE, body);
1337 case COMPOSE_REPLY_WITH_QUOTE:
1338 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1339 FALSE, prefs_common.default_reply_list, FALSE, body);
1341 case COMPOSE_REPLY_WITHOUT_QUOTE:
1342 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1343 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1345 case COMPOSE_REPLY_TO_SENDER:
1346 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1347 FALSE, FALSE, TRUE, body);
1349 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1350 compose = compose_followup_and_reply_to(msginfo,
1351 COMPOSE_QUOTE_CHECK,
1352 FALSE, FALSE, body);
1354 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1356 FALSE, FALSE, TRUE, body);
1358 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1360 FALSE, FALSE, TRUE, NULL);
1362 case COMPOSE_REPLY_TO_ALL:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1364 TRUE, FALSE, FALSE, body);
1366 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1367 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1368 TRUE, FALSE, FALSE, body);
1370 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1371 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1372 TRUE, FALSE, FALSE, NULL);
1374 case COMPOSE_REPLY_TO_LIST:
1375 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1376 FALSE, TRUE, FALSE, body);
1378 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1379 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1380 FALSE, TRUE, FALSE, body);
1382 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1383 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1384 FALSE, TRUE, FALSE, NULL);
1386 case COMPOSE_FORWARD:
1387 if (prefs_common.forward_as_attachment) {
1388 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1391 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1395 case COMPOSE_FORWARD_INLINE:
1396 /* check if we reply to more than one Message */
1397 if (list_len == 1) {
1398 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1401 /* more messages FALL THROUGH */
1402 case COMPOSE_FORWARD_AS_ATTACH:
1403 compose = compose_forward_multiple(NULL, msginfo_list);
1405 case COMPOSE_REDIRECT:
1406 compose = compose_redirect(NULL, msginfo, FALSE);
1409 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1412 if (compose == NULL) {
1413 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1417 compose->rmode = mode;
1418 switch (compose->rmode) {
1420 case COMPOSE_REPLY_WITH_QUOTE:
1421 case COMPOSE_REPLY_WITHOUT_QUOTE:
1422 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1423 debug_print("reply mode Normal\n");
1424 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1425 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1427 case COMPOSE_REPLY_TO_SENDER:
1428 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1429 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1430 debug_print("reply mode Sender\n");
1431 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1433 case COMPOSE_REPLY_TO_ALL:
1434 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1435 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1436 debug_print("reply mode All\n");
1437 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1439 case COMPOSE_REPLY_TO_LIST:
1440 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1441 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1442 debug_print("reply mode List\n");
1443 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1445 case COMPOSE_REPLY_TO_ADDRESS:
1446 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1454 static Compose *compose_reply(MsgInfo *msginfo,
1455 ComposeQuoteMode quote_mode,
1461 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1462 to_sender, FALSE, body);
1465 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1466 ComposeQuoteMode quote_mode,
1471 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1472 to_sender, TRUE, body);
1475 static void compose_extract_original_charset(Compose *compose)
1477 MsgInfo *info = NULL;
1478 if (compose->replyinfo) {
1479 info = compose->replyinfo;
1480 } else if (compose->fwdinfo) {
1481 info = compose->fwdinfo;
1482 } else if (compose->targetinfo) {
1483 info = compose->targetinfo;
1486 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1487 MimeInfo *partinfo = mimeinfo;
1488 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1489 partinfo = procmime_mimeinfo_next(partinfo);
1491 compose->orig_charset =
1492 g_strdup(procmime_mimeinfo_get_parameter(
1493 partinfo, "charset"));
1495 procmime_mimeinfo_free_all(mimeinfo);
1499 #define SIGNAL_BLOCK(buffer) { \
1500 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1501 G_CALLBACK(compose_changed_cb), \
1503 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1504 G_CALLBACK(text_inserted), \
1508 #define SIGNAL_UNBLOCK(buffer) { \
1509 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1510 G_CALLBACK(compose_changed_cb), \
1512 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1513 G_CALLBACK(text_inserted), \
1517 static Compose *compose_generic_reply(MsgInfo *msginfo,
1518 ComposeQuoteMode quote_mode,
1519 gboolean to_all, gboolean to_ml,
1521 gboolean followup_and_reply_to,
1525 PrefsAccount *account = NULL;
1526 GtkTextView *textview;
1527 GtkTextBuffer *textbuf;
1528 gboolean quote = FALSE;
1529 const gchar *qmark = NULL;
1530 const gchar *body_fmt = NULL;
1531 gchar *s_system = NULL;
1533 cm_return_val_if_fail(msginfo != NULL, NULL);
1534 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1536 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1538 cm_return_val_if_fail(account != NULL, NULL);
1540 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1542 compose->updating = TRUE;
1544 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1547 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1548 if (!compose->replyinfo)
1549 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1551 compose_extract_original_charset(compose);
1553 if (msginfo->folder && msginfo->folder->ret_rcpt)
1554 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1556 /* Set save folder */
1557 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1558 gchar *folderidentifier;
1560 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1561 folderidentifier = folder_item_get_identifier(msginfo->folder);
1562 compose_set_save_to(compose, folderidentifier);
1563 g_free(folderidentifier);
1566 if (compose_parse_header(compose, msginfo) < 0) {
1567 compose->updating = FALSE;
1568 compose_destroy(compose);
1572 /* override from name according to folder properties */
1573 if (msginfo->folder && msginfo->folder->prefs &&
1574 msginfo->folder->prefs->reply_with_format &&
1575 msginfo->folder->prefs->reply_override_from_format &&
1576 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1581 /* decode \-escape sequences in the internal representation of the quote format */
1582 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1583 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1586 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1587 compose->gtkaspell);
1589 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1591 quote_fmt_scan_string(tmp);
1594 buf = quote_fmt_get_buffer();
1596 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1598 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1599 quote_fmt_reset_vartable();
1604 textview = (GTK_TEXT_VIEW(compose->text));
1605 textbuf = gtk_text_view_get_buffer(textview);
1606 compose_create_tags(textview, compose);
1608 undo_block(compose->undostruct);
1610 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1611 gtkaspell_block_check(compose->gtkaspell);
1614 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1615 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1616 /* use the reply format of folder (if enabled), or the account's one
1617 (if enabled) or fallback to the global reply format, which is always
1618 enabled (even if empty), and use the relevant quotemark */
1620 if (msginfo->folder && msginfo->folder->prefs &&
1621 msginfo->folder->prefs->reply_with_format) {
1622 qmark = msginfo->folder->prefs->reply_quotemark;
1623 body_fmt = msginfo->folder->prefs->reply_body_format;
1625 } else if (account->reply_with_format) {
1626 qmark = account->reply_quotemark;
1627 body_fmt = account->reply_body_format;
1630 qmark = prefs_common.quotemark;
1631 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1632 body_fmt = gettext(prefs_common.quotefmt);
1639 /* empty quotemark is not allowed */
1640 if (qmark == NULL || *qmark == '\0')
1642 compose_quote_fmt(compose, compose->replyinfo,
1643 body_fmt, qmark, body, FALSE, TRUE,
1644 _("The body of the \"Reply\" template has an error at line %d."));
1645 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1646 quote_fmt_reset_vartable();
1649 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1650 compose_force_encryption(compose, account, FALSE, s_system);
1653 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1654 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1655 compose_force_signing(compose, account, s_system);
1659 SIGNAL_BLOCK(textbuf);
1661 if (account->auto_sig)
1662 compose_insert_sig(compose, FALSE);
1664 compose_wrap_all(compose);
1667 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1668 gtkaspell_highlight_all(compose->gtkaspell);
1669 gtkaspell_unblock_check(compose->gtkaspell);
1671 SIGNAL_UNBLOCK(textbuf);
1673 gtk_widget_grab_focus(compose->text);
1675 undo_unblock(compose->undostruct);
1677 if (prefs_common.auto_exteditor)
1678 compose_exec_ext_editor(compose);
1680 compose->modified = FALSE;
1681 compose_set_title(compose);
1683 compose->updating = FALSE;
1684 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1685 SCROLL_TO_CURSOR(compose);
1687 if (compose->deferred_destroy) {
1688 compose_destroy(compose);
1696 #define INSERT_FW_HEADER(var, hdr) \
1697 if (msginfo->var && *msginfo->var) { \
1698 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1699 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1700 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1703 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1704 gboolean as_attach, const gchar *body,
1705 gboolean no_extedit,
1709 GtkTextView *textview;
1710 GtkTextBuffer *textbuf;
1711 gint cursor_pos = -1;
1714 cm_return_val_if_fail(msginfo != NULL, NULL);
1715 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1718 !(account = compose_guess_forward_account_from_msginfo
1720 account = cur_account;
1722 if (!prefs_common.forward_as_attachment)
1723 mode = COMPOSE_FORWARD_INLINE;
1725 mode = COMPOSE_FORWARD;
1726 compose = compose_create(account, msginfo->folder, mode, batch);
1728 compose->updating = TRUE;
1729 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1730 if (!compose->fwdinfo)
1731 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1733 compose_extract_original_charset(compose);
1735 if (msginfo->subject && *msginfo->subject) {
1736 gchar *buf, *buf2, *p;
1738 buf = p = g_strdup(msginfo->subject);
1739 p += subject_get_prefix_length(p);
1740 memmove(buf, p, strlen(p) + 1);
1742 buf2 = g_strdup_printf("Fw: %s", buf);
1743 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1749 /* override from name according to folder properties */
1750 if (msginfo->folder && msginfo->folder->prefs &&
1751 msginfo->folder->prefs->forward_with_format &&
1752 msginfo->folder->prefs->forward_override_from_format &&
1753 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1757 MsgInfo *full_msginfo = NULL;
1760 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1762 full_msginfo = procmsg_msginfo_copy(msginfo);
1764 /* decode \-escape sequences in the internal representation of the quote format */
1765 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1766 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1769 gtkaspell_block_check(compose->gtkaspell);
1770 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1771 compose->gtkaspell);
1773 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1775 quote_fmt_scan_string(tmp);
1778 buf = quote_fmt_get_buffer();
1780 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1782 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1783 quote_fmt_reset_vartable();
1786 procmsg_msginfo_free(full_msginfo);
1789 textview = GTK_TEXT_VIEW(compose->text);
1790 textbuf = gtk_text_view_get_buffer(textview);
1791 compose_create_tags(textview, compose);
1793 undo_block(compose->undostruct);
1797 msgfile = procmsg_get_message_file(msginfo);
1798 if (!is_file_exist(msgfile))
1799 g_warning("%s: file not exist\n", msgfile);
1801 compose_attach_append(compose, msgfile, msgfile,
1802 "message/rfc822", NULL);
1806 const gchar *qmark = NULL;
1807 const gchar *body_fmt = NULL;
1808 MsgInfo *full_msginfo;
1810 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1812 full_msginfo = procmsg_msginfo_copy(msginfo);
1814 /* use the forward format of folder (if enabled), or the account's one
1815 (if enabled) or fallback to the global forward format, which is always
1816 enabled (even if empty), and use the relevant quotemark */
1817 if (msginfo->folder && msginfo->folder->prefs &&
1818 msginfo->folder->prefs->forward_with_format) {
1819 qmark = msginfo->folder->prefs->forward_quotemark;
1820 body_fmt = msginfo->folder->prefs->forward_body_format;
1822 } else if (account->forward_with_format) {
1823 qmark = account->forward_quotemark;
1824 body_fmt = account->forward_body_format;
1827 qmark = prefs_common.fw_quotemark;
1828 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1829 body_fmt = gettext(prefs_common.fw_quotefmt);
1834 /* empty quotemark is not allowed */
1835 if (qmark == NULL || *qmark == '\0')
1838 compose_quote_fmt(compose, full_msginfo,
1839 body_fmt, qmark, body, FALSE, TRUE,
1840 _("The body of the \"Forward\" template has an error at line %d."));
1841 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1842 quote_fmt_reset_vartable();
1843 compose_attach_parts(compose, msginfo);
1845 procmsg_msginfo_free(full_msginfo);
1848 SIGNAL_BLOCK(textbuf);
1850 if (account->auto_sig)
1851 compose_insert_sig(compose, FALSE);
1853 compose_wrap_all(compose);
1856 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1857 gtkaspell_highlight_all(compose->gtkaspell);
1858 gtkaspell_unblock_check(compose->gtkaspell);
1860 SIGNAL_UNBLOCK(textbuf);
1862 cursor_pos = quote_fmt_get_cursor_pos();
1863 if (cursor_pos == -1)
1864 gtk_widget_grab_focus(compose->header_last->entry);
1866 gtk_widget_grab_focus(compose->text);
1868 if (!no_extedit && prefs_common.auto_exteditor)
1869 compose_exec_ext_editor(compose);
1872 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1873 gchar *folderidentifier;
1875 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1876 folderidentifier = folder_item_get_identifier(msginfo->folder);
1877 compose_set_save_to(compose, folderidentifier);
1878 g_free(folderidentifier);
1881 undo_unblock(compose->undostruct);
1883 compose->modified = FALSE;
1884 compose_set_title(compose);
1886 compose->updating = FALSE;
1887 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1888 SCROLL_TO_CURSOR(compose);
1890 if (compose->deferred_destroy) {
1891 compose_destroy(compose);
1895 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1900 #undef INSERT_FW_HEADER
1902 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1905 GtkTextView *textview;
1906 GtkTextBuffer *textbuf;
1910 gboolean single_mail = TRUE;
1912 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1914 if (g_slist_length(msginfo_list) > 1)
1915 single_mail = FALSE;
1917 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1918 if (((MsgInfo *)msginfo->data)->folder == NULL)
1921 /* guess account from first selected message */
1923 !(account = compose_guess_forward_account_from_msginfo
1924 (msginfo_list->data)))
1925 account = cur_account;
1927 cm_return_val_if_fail(account != NULL, NULL);
1929 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1930 if (msginfo->data) {
1931 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1932 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1936 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1937 g_warning("no msginfo_list");
1941 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1943 compose->updating = TRUE;
1945 /* override from name according to folder properties */
1946 if (msginfo_list->data) {
1947 MsgInfo *msginfo = msginfo_list->data;
1949 if (msginfo->folder && msginfo->folder->prefs &&
1950 msginfo->folder->prefs->forward_with_format &&
1951 msginfo->folder->prefs->forward_override_from_format &&
1952 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1957 /* decode \-escape sequences in the internal representation of the quote format */
1958 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1959 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1962 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1963 compose->gtkaspell);
1965 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1967 quote_fmt_scan_string(tmp);
1970 buf = quote_fmt_get_buffer();
1972 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1975 quote_fmt_reset_vartable();
1981 textview = GTK_TEXT_VIEW(compose->text);
1982 textbuf = gtk_text_view_get_buffer(textview);
1983 compose_create_tags(textview, compose);
1985 undo_block(compose->undostruct);
1986 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1987 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1989 if (!is_file_exist(msgfile))
1990 g_warning("%s: file not exist\n", msgfile);
1992 compose_attach_append(compose, msgfile, msgfile,
1993 "message/rfc822", NULL);
1998 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1999 if (info->subject && *info->subject) {
2000 gchar *buf, *buf2, *p;
2002 buf = p = g_strdup(info->subject);
2003 p += subject_get_prefix_length(p);
2004 memmove(buf, p, strlen(p) + 1);
2006 buf2 = g_strdup_printf("Fw: %s", buf);
2007 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2013 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2014 _("Fw: multiple emails"));
2017 SIGNAL_BLOCK(textbuf);
2019 if (account->auto_sig)
2020 compose_insert_sig(compose, FALSE);
2022 compose_wrap_all(compose);
2024 SIGNAL_UNBLOCK(textbuf);
2026 gtk_text_buffer_get_start_iter(textbuf, &iter);
2027 gtk_text_buffer_place_cursor(textbuf, &iter);
2029 gtk_widget_grab_focus(compose->header_last->entry);
2030 undo_unblock(compose->undostruct);
2031 compose->modified = FALSE;
2032 compose_set_title(compose);
2034 compose->updating = FALSE;
2035 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2036 SCROLL_TO_CURSOR(compose);
2038 if (compose->deferred_destroy) {
2039 compose_destroy(compose);
2043 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2048 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2050 GtkTextIter start = *iter;
2051 GtkTextIter end_iter;
2052 int start_pos = gtk_text_iter_get_offset(&start);
2054 if (!compose->account->sig_sep)
2057 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2058 start_pos+strlen(compose->account->sig_sep));
2060 /* check sig separator */
2061 str = gtk_text_iter_get_text(&start, &end_iter);
2062 if (!strcmp(str, compose->account->sig_sep)) {
2064 /* check end of line (\n) */
2065 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2066 start_pos+strlen(compose->account->sig_sep));
2067 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2068 start_pos+strlen(compose->account->sig_sep)+1);
2069 tmp = gtk_text_iter_get_text(&start, &end_iter);
2070 if (!strcmp(tmp,"\n")) {
2082 static void compose_colorize_signature(Compose *compose)
2084 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2086 GtkTextIter end_iter;
2087 gtk_text_buffer_get_start_iter(buffer, &iter);
2088 while (gtk_text_iter_forward_line(&iter))
2089 if (compose_is_sig_separator(compose, buffer, &iter)) {
2090 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2091 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2095 #define BLOCK_WRAP() { \
2096 prev_autowrap = compose->autowrap; \
2097 buffer = gtk_text_view_get_buffer( \
2098 GTK_TEXT_VIEW(compose->text)); \
2099 compose->autowrap = FALSE; \
2101 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2102 G_CALLBACK(compose_changed_cb), \
2104 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2105 G_CALLBACK(text_inserted), \
2108 #define UNBLOCK_WRAP() { \
2109 compose->autowrap = prev_autowrap; \
2110 if (compose->autowrap) { \
2111 gint old = compose->draft_timeout_tag; \
2112 compose->draft_timeout_tag = -2; \
2113 compose_wrap_all(compose); \
2114 compose->draft_timeout_tag = old; \
2117 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2118 G_CALLBACK(compose_changed_cb), \
2120 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2121 G_CALLBACK(text_inserted), \
2125 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2127 Compose *compose = NULL;
2128 PrefsAccount *account = NULL;
2129 GtkTextView *textview;
2130 GtkTextBuffer *textbuf;
2134 gchar buf[BUFFSIZE];
2135 gboolean use_signing = FALSE;
2136 gboolean use_encryption = FALSE;
2137 gchar *privacy_system = NULL;
2138 int priority = PRIORITY_NORMAL;
2139 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2140 gboolean autowrap = prefs_common.autowrap;
2141 gboolean autoindent = prefs_common.auto_indent;
2142 HeaderEntry *manual_headers = NULL;
2144 cm_return_val_if_fail(msginfo != NULL, NULL);
2145 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2147 if (compose_put_existing_to_front(msginfo)) {
2151 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2152 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2153 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2154 gchar queueheader_buf[BUFFSIZE];
2157 /* Select Account from queue headers */
2158 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2159 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2160 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2161 account = account_find_from_id(id);
2163 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2164 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2165 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2166 account = account_find_from_id(id);
2168 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2169 sizeof(queueheader_buf), "NAID:")) {
2170 id = atoi(&queueheader_buf[strlen("NAID:")]);
2171 account = account_find_from_id(id);
2173 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2174 sizeof(queueheader_buf), "MAID:")) {
2175 id = atoi(&queueheader_buf[strlen("MAID:")]);
2176 account = account_find_from_id(id);
2178 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2179 sizeof(queueheader_buf), "S:")) {
2180 account = account_find_from_address(queueheader_buf, FALSE);
2182 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2183 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2184 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2185 use_signing = param;
2188 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2189 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2190 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2191 use_signing = param;
2194 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2195 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2196 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2197 use_encryption = param;
2199 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2201 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2202 use_encryption = param;
2204 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2205 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2206 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2209 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2210 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2211 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2214 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2215 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2216 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2218 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2219 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2220 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2222 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2223 sizeof(queueheader_buf), "X-Priority: ")) {
2224 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2227 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2228 sizeof(queueheader_buf), "RMID:")) {
2229 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2230 if (tokens[0] && tokens[1] && tokens[2]) {
2231 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2232 if (orig_item != NULL) {
2233 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2238 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2239 sizeof(queueheader_buf), "FMID:")) {
2240 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2241 if (tokens[0] && tokens[1] && tokens[2]) {
2242 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2243 if (orig_item != NULL) {
2244 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2249 /* Get manual headers */
2250 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2251 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2252 if (*listmh != '\0') {
2253 debug_print("Got manual headers: %s\n", listmh);
2254 manual_headers = procheader_entries_from_str(listmh);
2259 account = msginfo->folder->folder->account;
2262 if (!account && prefs_common.reedit_account_autosel) {
2263 gchar from[BUFFSIZE];
2264 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2265 extract_address(from);
2266 account = account_find_from_address(from, FALSE);
2270 account = cur_account;
2272 cm_return_val_if_fail(account != NULL, NULL);
2274 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2276 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2277 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2278 compose->autowrap = autowrap;
2279 compose->replyinfo = replyinfo;
2280 compose->fwdinfo = fwdinfo;
2282 compose->updating = TRUE;
2283 compose->priority = priority;
2285 if (privacy_system != NULL) {
2286 compose->privacy_system = privacy_system;
2287 compose_use_signing(compose, use_signing);
2288 compose_use_encryption(compose, use_encryption);
2289 compose_update_privacy_system_menu_item(compose, FALSE);
2291 activate_privacy_system(compose, account, FALSE);
2294 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2296 compose_extract_original_charset(compose);
2298 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2299 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2300 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2301 gchar queueheader_buf[BUFFSIZE];
2303 /* Set message save folder */
2304 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2305 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2306 compose_set_save_to(compose, &queueheader_buf[4]);
2308 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2309 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2311 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2316 if (compose_parse_header(compose, msginfo) < 0) {
2317 compose->updating = FALSE;
2318 compose_destroy(compose);
2321 compose_reedit_set_entry(compose, msginfo);
2323 textview = GTK_TEXT_VIEW(compose->text);
2324 textbuf = gtk_text_view_get_buffer(textview);
2325 compose_create_tags(textview, compose);
2327 mark = gtk_text_buffer_get_insert(textbuf);
2328 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2330 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2331 G_CALLBACK(compose_changed_cb),
2334 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2335 fp = procmime_get_first_encrypted_text_content(msginfo);
2337 compose_force_encryption(compose, account, TRUE, NULL);
2340 fp = procmime_get_first_text_content(msginfo);
2343 g_warning("Can't get text part\n");
2347 gboolean prev_autowrap;
2348 GtkTextBuffer *buffer;
2350 while (fgets(buf, sizeof(buf), fp) != NULL) {
2352 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2358 compose_attach_parts(compose, msginfo);
2360 compose_colorize_signature(compose);
2362 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2363 G_CALLBACK(compose_changed_cb),
2366 if (manual_headers != NULL) {
2367 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2368 procheader_entries_free(manual_headers);
2369 compose->updating = FALSE;
2370 compose_destroy(compose);
2373 procheader_entries_free(manual_headers);
2376 gtk_widget_grab_focus(compose->text);
2378 if (prefs_common.auto_exteditor) {
2379 compose_exec_ext_editor(compose);
2381 compose->modified = FALSE;
2382 compose_set_title(compose);
2384 compose->updating = FALSE;
2385 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2386 SCROLL_TO_CURSOR(compose);
2388 if (compose->deferred_destroy) {
2389 compose_destroy(compose);
2393 compose->sig_str = account_get_signature_str(compose->account);
2395 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2400 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2407 cm_return_val_if_fail(msginfo != NULL, NULL);
2410 account = account_get_reply_account(msginfo,
2411 prefs_common.reply_account_autosel);
2412 cm_return_val_if_fail(account != NULL, NULL);
2414 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2416 compose->updating = TRUE;
2418 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2419 compose->replyinfo = NULL;
2420 compose->fwdinfo = NULL;
2422 compose_show_first_last_header(compose, TRUE);
2424 gtk_widget_grab_focus(compose->header_last->entry);
2426 filename = procmsg_get_message_file(msginfo);
2428 if (filename == NULL) {
2429 compose->updating = FALSE;
2430 compose_destroy(compose);
2435 compose->redirect_filename = filename;
2437 /* Set save folder */
2438 item = msginfo->folder;
2439 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2440 gchar *folderidentifier;
2442 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2443 folderidentifier = folder_item_get_identifier(item);
2444 compose_set_save_to(compose, folderidentifier);
2445 g_free(folderidentifier);
2448 compose_attach_parts(compose, msginfo);
2450 if (msginfo->subject)
2451 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2453 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2455 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2456 _("The body of the \"Redirect\" template has an error at line %d."));
2457 quote_fmt_reset_vartable();
2458 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2460 compose_colorize_signature(compose);
2463 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2464 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2465 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2470 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2471 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2476 if (compose->toolbar->draft_btn)
2477 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2478 if (compose->toolbar->insert_btn)
2479 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2480 if (compose->toolbar->attach_btn)
2481 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2482 if (compose->toolbar->sig_btn)
2483 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2484 if (compose->toolbar->exteditor_btn)
2485 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2486 if (compose->toolbar->linewrap_current_btn)
2487 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2488 if (compose->toolbar->linewrap_all_btn)
2489 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2491 compose->modified = FALSE;
2492 compose_set_title(compose);
2493 compose->updating = FALSE;
2494 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2495 SCROLL_TO_CURSOR(compose);
2497 if (compose->deferred_destroy) {
2498 compose_destroy(compose);
2502 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2507 GList *compose_get_compose_list(void)
2509 return compose_list;
2512 void compose_entry_append(Compose *compose, const gchar *address,
2513 ComposeEntryType type, ComposePrefType pref_type)
2515 const gchar *header;
2517 gboolean in_quote = FALSE;
2518 if (!address || *address == '\0') return;
2525 header = N_("Bcc:");
2527 case COMPOSE_REPLYTO:
2528 header = N_("Reply-To:");
2530 case COMPOSE_NEWSGROUPS:
2531 header = N_("Newsgroups:");
2533 case COMPOSE_FOLLOWUPTO:
2534 header = N_( "Followup-To:");
2536 case COMPOSE_INREPLYTO:
2537 header = N_( "In-Reply-To:");
2544 header = prefs_common_translated_header_name(header);
2546 cur = begin = (gchar *)address;
2548 /* we separate the line by commas, but not if we're inside a quoted
2550 while (*cur != '\0') {
2552 in_quote = !in_quote;
2553 if (*cur == ',' && !in_quote) {
2554 gchar *tmp = g_strdup(begin);
2556 tmp[cur-begin]='\0';
2559 while (*tmp == ' ' || *tmp == '\t')
2561 compose_add_header_entry(compose, header, tmp, pref_type);
2568 gchar *tmp = g_strdup(begin);
2570 tmp[cur-begin]='\0';
2571 while (*tmp == ' ' || *tmp == '\t')
2573 compose_add_header_entry(compose, header, tmp, pref_type);
2578 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2580 #if !GTK_CHECK_VERSION(3, 0, 0)
2581 static GdkColor yellow;
2582 static GdkColor black;
2583 static gboolean yellow_initialised = FALSE;
2585 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2586 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2591 #if !GTK_CHECK_VERSION(3, 0, 0)
2592 if (!yellow_initialised) {
2593 gdk_color_parse("#f5f6be", &yellow);
2594 gdk_color_parse("#000000", &black);
2595 yellow_initialised = gdk_colormap_alloc_color(
2596 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2597 yellow_initialised &= gdk_colormap_alloc_color(
2598 gdk_colormap_get_system(), &black, FALSE, TRUE);
2602 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2603 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2604 if (gtk_entry_get_text(entry) &&
2605 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2606 #if !GTK_CHECK_VERSION(3, 0, 0)
2607 if (yellow_initialised) {
2609 gtk_widget_modify_base(
2610 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2611 GTK_STATE_NORMAL, &yellow);
2612 gtk_widget_modify_text(
2613 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2614 GTK_STATE_NORMAL, &black);
2615 #if !GTK_CHECK_VERSION(3, 0, 0)
2622 void compose_toolbar_cb(gint action, gpointer data)
2624 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2625 Compose *compose = (Compose*)toolbar_item->parent;
2627 cm_return_if_fail(compose != NULL);
2631 compose_send_cb(NULL, compose);
2634 compose_send_later_cb(NULL, compose);
2637 compose_draft(compose, COMPOSE_QUIT_EDITING);
2640 compose_insert_file_cb(NULL, compose);
2643 compose_attach_cb(NULL, compose);
2646 compose_insert_sig(compose, FALSE);
2649 compose_ext_editor_cb(NULL, compose);
2651 case A_LINEWRAP_CURRENT:
2652 compose_beautify_paragraph(compose, NULL, TRUE);
2654 case A_LINEWRAP_ALL:
2655 compose_wrap_all_full(compose, TRUE);
2658 compose_address_cb(NULL, compose);
2661 case A_CHECK_SPELLING:
2662 compose_check_all(NULL, compose);
2670 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2675 gchar *subject = NULL;
2679 gchar **attach = NULL;
2680 gchar *inreplyto = NULL;
2681 MailField mfield = NO_FIELD_PRESENT;
2683 /* get mailto parts but skip from */
2684 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2687 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2688 mfield = TO_FIELD_PRESENT;
2691 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2693 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2695 if (!g_utf8_validate (subject, -1, NULL)) {
2696 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2697 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2700 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2702 mfield = SUBJECT_FIELD_PRESENT;
2705 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2706 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2709 gboolean prev_autowrap = compose->autowrap;
2711 compose->autowrap = FALSE;
2713 mark = gtk_text_buffer_get_insert(buffer);
2714 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2716 if (!g_utf8_validate (body, -1, NULL)) {
2717 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2718 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2721 gtk_text_buffer_insert(buffer, &iter, body, -1);
2723 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2725 compose->autowrap = prev_autowrap;
2726 if (compose->autowrap)
2727 compose_wrap_all(compose);
2728 mfield = BODY_FIELD_PRESENT;
2732 gint i = 0, att = 0;
2733 gchar *warn_files = NULL;
2734 while (attach[i] != NULL) {
2735 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2736 if (utf8_filename) {
2737 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2738 gchar *tmp = g_strdup_printf("%s%s\n",
2739 warn_files?warn_files:"",
2745 g_free(utf8_filename);
2747 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2752 alertpanel_notice(ngettext(
2753 "The following file has been attached: \n%s",
2754 "The following files have been attached: \n%s", att), warn_files);
2759 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2772 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2774 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2775 {"Cc:", NULL, TRUE},
2776 {"References:", NULL, FALSE},
2777 {"Bcc:", NULL, TRUE},
2778 {"Newsgroups:", NULL, TRUE},
2779 {"Followup-To:", NULL, TRUE},
2780 {"List-Post:", NULL, FALSE},
2781 {"X-Priority:", NULL, FALSE},
2782 {NULL, NULL, FALSE}};
2798 cm_return_val_if_fail(msginfo != NULL, -1);
2800 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2801 procheader_get_header_fields(fp, hentry);
2804 if (hentry[H_REPLY_TO].body != NULL) {
2805 if (hentry[H_REPLY_TO].body[0] != '\0') {
2807 conv_unmime_header(hentry[H_REPLY_TO].body,
2810 g_free(hentry[H_REPLY_TO].body);
2811 hentry[H_REPLY_TO].body = NULL;
2813 if (hentry[H_CC].body != NULL) {
2814 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2815 g_free(hentry[H_CC].body);
2816 hentry[H_CC].body = NULL;
2818 if (hentry[H_REFERENCES].body != NULL) {
2819 if (compose->mode == COMPOSE_REEDIT)
2820 compose->references = hentry[H_REFERENCES].body;
2822 compose->references = compose_parse_references
2823 (hentry[H_REFERENCES].body, msginfo->msgid);
2824 g_free(hentry[H_REFERENCES].body);
2826 hentry[H_REFERENCES].body = NULL;
2828 if (hentry[H_BCC].body != NULL) {
2829 if (compose->mode == COMPOSE_REEDIT)
2831 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2832 g_free(hentry[H_BCC].body);
2833 hentry[H_BCC].body = NULL;
2835 if (hentry[H_NEWSGROUPS].body != NULL) {
2836 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2837 hentry[H_NEWSGROUPS].body = NULL;
2839 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2840 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2841 compose->followup_to =
2842 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2845 g_free(hentry[H_FOLLOWUP_TO].body);
2846 hentry[H_FOLLOWUP_TO].body = NULL;
2848 if (hentry[H_LIST_POST].body != NULL) {
2849 gchar *to = NULL, *start = NULL;
2851 extract_address(hentry[H_LIST_POST].body);
2852 if (hentry[H_LIST_POST].body[0] != '\0') {
2853 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2855 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2856 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2859 g_free(compose->ml_post);
2860 compose->ml_post = to;
2863 g_free(hentry[H_LIST_POST].body);
2864 hentry[H_LIST_POST].body = NULL;
2867 /* CLAWS - X-Priority */
2868 if (compose->mode == COMPOSE_REEDIT)
2869 if (hentry[H_X_PRIORITY].body != NULL) {
2872 priority = atoi(hentry[H_X_PRIORITY].body);
2873 g_free(hentry[H_X_PRIORITY].body);
2875 hentry[H_X_PRIORITY].body = NULL;
2877 if (priority < PRIORITY_HIGHEST ||
2878 priority > PRIORITY_LOWEST)
2879 priority = PRIORITY_NORMAL;
2881 compose->priority = priority;
2884 if (compose->mode == COMPOSE_REEDIT) {
2885 if (msginfo->inreplyto && *msginfo->inreplyto)
2886 compose->inreplyto = g_strdup(msginfo->inreplyto);
2890 if (msginfo->msgid && *msginfo->msgid)
2891 compose->inreplyto = g_strdup(msginfo->msgid);
2893 if (!compose->references) {
2894 if (msginfo->msgid && *msginfo->msgid) {
2895 if (msginfo->inreplyto && *msginfo->inreplyto)
2896 compose->references =
2897 g_strdup_printf("<%s>\n\t<%s>",
2901 compose->references =
2902 g_strconcat("<", msginfo->msgid, ">",
2904 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2905 compose->references =
2906 g_strconcat("<", msginfo->inreplyto, ">",
2914 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2919 cm_return_val_if_fail(msginfo != NULL, -1);
2921 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2922 procheader_get_header_fields(fp, entries);
2926 while (he != NULL && he->name != NULL) {
2928 GtkListStore *model = NULL;
2930 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2931 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2932 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2933 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2934 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2941 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2943 GSList *ref_id_list, *cur;
2947 ref_id_list = references_list_append(NULL, ref);
2948 if (!ref_id_list) return NULL;
2949 if (msgid && *msgid)
2950 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2955 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2956 /* "<" + Message-ID + ">" + CR+LF+TAB */
2957 len += strlen((gchar *)cur->data) + 5;
2959 if (len > MAX_REFERENCES_LEN) {
2960 /* remove second message-ID */
2961 if (ref_id_list && ref_id_list->next &&
2962 ref_id_list->next->next) {
2963 g_free(ref_id_list->next->data);
2964 ref_id_list = g_slist_remove
2965 (ref_id_list, ref_id_list->next->data);
2967 slist_free_strings_full(ref_id_list);
2974 new_ref = g_string_new("");
2975 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2976 if (new_ref->len > 0)
2977 g_string_append(new_ref, "\n\t");
2978 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2981 slist_free_strings_full(ref_id_list);
2983 new_ref_str = new_ref->str;
2984 g_string_free(new_ref, FALSE);
2989 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2990 const gchar *fmt, const gchar *qmark,
2991 const gchar *body, gboolean rewrap,
2992 gboolean need_unescape,
2993 const gchar *err_msg)
2995 MsgInfo* dummyinfo = NULL;
2996 gchar *quote_str = NULL;
2998 gboolean prev_autowrap;
2999 const gchar *trimmed_body = body;
3000 gint cursor_pos = -1;
3001 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3002 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3007 SIGNAL_BLOCK(buffer);
3010 dummyinfo = compose_msginfo_new_from_compose(compose);
3011 msginfo = dummyinfo;
3014 if (qmark != NULL) {
3016 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3017 compose->gtkaspell);
3019 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3021 quote_fmt_scan_string(qmark);
3024 buf = quote_fmt_get_buffer();
3026 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3028 Xstrdup_a(quote_str, buf, goto error)
3031 if (fmt && *fmt != '\0') {
3034 while (*trimmed_body == '\n')
3038 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3039 compose->gtkaspell);
3041 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3043 if (need_unescape) {
3046 /* decode \-escape sequences in the internal representation of the quote format */
3047 tmp = g_malloc(strlen(fmt)+1);
3048 pref_get_unescaped_pref(tmp, fmt);
3049 quote_fmt_scan_string(tmp);
3053 quote_fmt_scan_string(fmt);
3057 buf = quote_fmt_get_buffer();
3059 gint line = quote_fmt_get_line();
3060 alertpanel_error(err_msg, line);
3066 prev_autowrap = compose->autowrap;
3067 compose->autowrap = FALSE;
3069 mark = gtk_text_buffer_get_insert(buffer);
3070 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3071 if (g_utf8_validate(buf, -1, NULL)) {
3072 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3074 gchar *tmpout = NULL;
3075 tmpout = conv_codeset_strdup
3076 (buf, conv_get_locale_charset_str_no_utf8(),
3078 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3080 tmpout = g_malloc(strlen(buf)*2+1);
3081 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3083 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3087 cursor_pos = quote_fmt_get_cursor_pos();
3088 if (cursor_pos == -1)
3089 cursor_pos = gtk_text_iter_get_offset(&iter);
3090 compose->set_cursor_pos = cursor_pos;
3092 gtk_text_buffer_get_start_iter(buffer, &iter);
3093 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3094 gtk_text_buffer_place_cursor(buffer, &iter);
3096 compose->autowrap = prev_autowrap;
3097 if (compose->autowrap && rewrap)
3098 compose_wrap_all(compose);
3105 SIGNAL_UNBLOCK(buffer);
3107 procmsg_msginfo_free( dummyinfo );
3112 /* if ml_post is of type addr@host and from is of type
3113 * addr-anything@host, return TRUE
3115 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3117 gchar *left_ml = NULL;
3118 gchar *right_ml = NULL;
3119 gchar *left_from = NULL;
3120 gchar *right_from = NULL;
3121 gboolean result = FALSE;
3123 if (!ml_post || !from)
3126 left_ml = g_strdup(ml_post);
3127 if (strstr(left_ml, "@")) {
3128 right_ml = strstr(left_ml, "@")+1;
3129 *(strstr(left_ml, "@")) = '\0';
3132 left_from = g_strdup(from);
3133 if (strstr(left_from, "@")) {
3134 right_from = strstr(left_from, "@")+1;
3135 *(strstr(left_from, "@")) = '\0';
3138 if (left_ml && left_from && right_ml && right_from
3139 && !strncmp(left_from, left_ml, strlen(left_ml))
3140 && !strcmp(right_from, right_ml)) {
3149 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3150 gboolean respect_default_to)
3154 if (!folder || !folder->prefs)
3157 if (respect_default_to && folder->prefs->enable_default_to) {
3158 compose_entry_append(compose, folder->prefs->default_to,
3159 COMPOSE_TO, PREF_FOLDER);
3160 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3162 if (folder->prefs->enable_default_cc)
3163 compose_entry_append(compose, folder->prefs->default_cc,
3164 COMPOSE_CC, PREF_FOLDER);
3165 if (folder->prefs->enable_default_bcc)
3166 compose_entry_append(compose, folder->prefs->default_bcc,
3167 COMPOSE_BCC, PREF_FOLDER);
3168 if (folder->prefs->enable_default_replyto)
3169 compose_entry_append(compose, folder->prefs->default_replyto,
3170 COMPOSE_REPLYTO, PREF_FOLDER);
3173 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3178 if (!compose || !msginfo)
3181 if (msginfo->subject && *msginfo->subject) {
3182 buf = p = g_strdup(msginfo->subject);
3183 p += subject_get_prefix_length(p);
3184 memmove(buf, p, strlen(p) + 1);
3186 buf2 = g_strdup_printf("Re: %s", buf);
3187 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3192 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3195 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3196 gboolean to_all, gboolean to_ml,
3198 gboolean followup_and_reply_to)
3200 GSList *cc_list = NULL;
3203 gchar *replyto = NULL;
3204 gchar *ac_email = NULL;
3206 gboolean reply_to_ml = FALSE;
3207 gboolean default_reply_to = FALSE;
3209 cm_return_if_fail(compose->account != NULL);
3210 cm_return_if_fail(msginfo != NULL);
3212 reply_to_ml = to_ml && compose->ml_post;
3214 default_reply_to = msginfo->folder &&
3215 msginfo->folder->prefs->enable_default_reply_to;
3217 if (compose->account->protocol != A_NNTP) {
3218 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3220 if (reply_to_ml && !default_reply_to) {
3222 gboolean is_subscr = is_subscription(compose->ml_post,
3225 /* normal answer to ml post with a reply-to */
3226 compose_entry_append(compose,
3228 COMPOSE_TO, PREF_ML);
3229 if (compose->replyto)
3230 compose_entry_append(compose,
3232 COMPOSE_CC, PREF_ML);
3234 /* answer to subscription confirmation */
3235 if (compose->replyto)
3236 compose_entry_append(compose,
3238 COMPOSE_TO, PREF_ML);
3239 else if (msginfo->from)
3240 compose_entry_append(compose,
3242 COMPOSE_TO, PREF_ML);
3245 else if (!(to_all || to_sender) && default_reply_to) {
3246 compose_entry_append(compose,
3247 msginfo->folder->prefs->default_reply_to,
3248 COMPOSE_TO, PREF_FOLDER);
3249 compose_entry_mark_default_to(compose,
3250 msginfo->folder->prefs->default_reply_to);
3255 Xstrdup_a(tmp1, msginfo->from, return);
3256 extract_address(tmp1);
3257 if (to_all || to_sender ||
3258 !account_find_from_address(tmp1, FALSE))
3259 compose_entry_append(compose,
3260 (compose->replyto && !to_sender)
3261 ? compose->replyto :
3262 msginfo->from ? msginfo->from : "",
3263 COMPOSE_TO, PREF_NONE);
3264 else if (!to_all && !to_sender) {
3265 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3266 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3267 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3268 if (compose->replyto) {
3269 compose_entry_append(compose,
3271 COMPOSE_TO, PREF_NONE);
3273 compose_entry_append(compose,
3274 msginfo->from ? msginfo->from : "",
3275 COMPOSE_TO, PREF_NONE);
3278 /* replying to own mail, use original recp */
3279 compose_entry_append(compose,
3280 msginfo->to ? msginfo->to : "",
3281 COMPOSE_TO, PREF_NONE);
3282 compose_entry_append(compose,
3283 msginfo->cc ? msginfo->cc : "",
3284 COMPOSE_CC, PREF_NONE);
3289 if (to_sender || (compose->followup_to &&
3290 !strncmp(compose->followup_to, "poster", 6)))
3291 compose_entry_append
3293 (compose->replyto ? compose->replyto :
3294 msginfo->from ? msginfo->from : ""),
3295 COMPOSE_TO, PREF_NONE);
3297 else if (followup_and_reply_to || to_all) {
3298 compose_entry_append
3300 (compose->replyto ? compose->replyto :
3301 msginfo->from ? msginfo->from : ""),
3302 COMPOSE_TO, PREF_NONE);
3304 compose_entry_append
3306 compose->followup_to ? compose->followup_to :
3307 compose->newsgroups ? compose->newsgroups : "",
3308 COMPOSE_NEWSGROUPS, PREF_NONE);
3311 compose_entry_append
3313 compose->followup_to ? compose->followup_to :
3314 compose->newsgroups ? compose->newsgroups : "",
3315 COMPOSE_NEWSGROUPS, PREF_NONE);
3317 compose_reply_set_subject(compose, msginfo);
3319 if (to_ml && compose->ml_post) return;
3320 if (!to_all || compose->account->protocol == A_NNTP) return;
3322 if (compose->replyto) {
3323 Xstrdup_a(replyto, compose->replyto, return);
3324 extract_address(replyto);
3326 if (msginfo->from) {
3327 Xstrdup_a(from, msginfo->from, return);
3328 extract_address(from);
3331 if (replyto && from)
3332 cc_list = address_list_append_with_comments(cc_list, from);
3333 if (to_all && msginfo->folder &&
3334 msginfo->folder->prefs->enable_default_reply_to)
3335 cc_list = address_list_append_with_comments(cc_list,
3336 msginfo->folder->prefs->default_reply_to);
3337 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3338 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3340 ac_email = g_utf8_strdown(compose->account->address, -1);
3343 for (cur = cc_list; cur != NULL; cur = cur->next) {
3344 gchar *addr = g_utf8_strdown(cur->data, -1);
3345 extract_address(addr);
3347 if (strcmp(ac_email, addr))
3348 compose_entry_append(compose, (gchar *)cur->data,
3349 COMPOSE_CC, PREF_NONE);
3351 debug_print("Cc address same as compose account's, ignoring\n");
3356 slist_free_strings_full(cc_list);
3362 #define SET_ENTRY(entry, str) \
3365 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3368 #define SET_ADDRESS(type, str) \
3371 compose_entry_append(compose, str, type, PREF_NONE); \
3374 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3376 cm_return_if_fail(msginfo != NULL);
3378 SET_ENTRY(subject_entry, msginfo->subject);
3379 SET_ENTRY(from_name, msginfo->from);
3380 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3381 SET_ADDRESS(COMPOSE_CC, compose->cc);
3382 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3383 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3384 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3385 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3387 compose_update_priority_menu_item(compose);
3388 compose_update_privacy_system_menu_item(compose, FALSE);
3389 compose_show_first_last_header(compose, TRUE);
3395 static void compose_insert_sig(Compose *compose, gboolean replace)
3397 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3398 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3400 GtkTextIter iter, iter_end;
3401 gint cur_pos, ins_pos;
3402 gboolean prev_autowrap;
3403 gboolean found = FALSE;
3404 gboolean exists = FALSE;
3406 cm_return_if_fail(compose->account != NULL);
3410 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3411 G_CALLBACK(compose_changed_cb),
3414 mark = gtk_text_buffer_get_insert(buffer);
3415 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3416 cur_pos = gtk_text_iter_get_offset (&iter);
3419 gtk_text_buffer_get_end_iter(buffer, &iter);
3421 exists = (compose->sig_str != NULL);
3424 GtkTextIter first_iter, start_iter, end_iter;
3426 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3428 if (!exists || compose->sig_str[0] == '\0')
3431 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3432 compose->signature_tag);
3435 /* include previous \n\n */
3436 gtk_text_iter_backward_chars(&first_iter, 1);
3437 start_iter = first_iter;
3438 end_iter = first_iter;
3440 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3441 compose->signature_tag);
3442 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3443 compose->signature_tag);
3445 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3451 g_free(compose->sig_str);
3452 compose->sig_str = account_get_signature_str(compose->account);
3454 cur_pos = gtk_text_iter_get_offset(&iter);
3456 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3457 g_free(compose->sig_str);
3458 compose->sig_str = NULL;
3460 if (compose->sig_inserted == FALSE)
3461 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3462 compose->sig_inserted = TRUE;
3464 cur_pos = gtk_text_iter_get_offset(&iter);
3465 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3467 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3468 gtk_text_iter_forward_chars(&iter, 1);
3469 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3470 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3472 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3473 cur_pos = gtk_text_buffer_get_char_count (buffer);
3476 /* put the cursor where it should be
3477 * either where the quote_fmt says, either where it was */
3478 if (compose->set_cursor_pos < 0)
3479 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3481 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3482 compose->set_cursor_pos);
3484 compose->set_cursor_pos = -1;
3485 gtk_text_buffer_place_cursor(buffer, &iter);
3486 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3487 G_CALLBACK(compose_changed_cb),
3493 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3496 GtkTextBuffer *buffer;
3499 const gchar *cur_encoding;
3500 gchar buf[BUFFSIZE];
3503 gboolean prev_autowrap;
3504 gboolean badtxt = FALSE;
3505 struct stat file_stat;
3507 GString *file_contents = NULL;
3509 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3511 /* get the size of the file we are about to insert */
3512 ret = g_stat(file, &file_stat);
3514 gchar *shortfile = g_path_get_basename(file);
3515 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3517 return COMPOSE_INSERT_NO_FILE;
3518 } else if (prefs_common.warn_large_insert == TRUE) {
3520 /* ask user for confirmation if the file is large */
3521 if (prefs_common.warn_large_insert_size < 0 ||
3522 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3526 msg = g_strdup_printf(_("You are about to insert a file of %s "
3527 "in the message body. Are you sure you want to do that?"),
3528 to_human_readable(file_stat.st_size));
3529 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3530 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3533 /* do we ask for confirmation next time? */
3534 if (aval & G_ALERTDISABLE) {
3535 /* no confirmation next time, disable feature in preferences */
3536 aval &= ~G_ALERTDISABLE;
3537 prefs_common.warn_large_insert = FALSE;
3540 /* abort file insertion if user canceled action */
3541 if (aval != G_ALERTALTERNATE) {
3542 return COMPOSE_INSERT_NO_FILE;
3548 if ((fp = g_fopen(file, "rb")) == NULL) {
3549 FILE_OP_ERROR(file, "fopen");
3550 return COMPOSE_INSERT_READ_ERROR;
3553 prev_autowrap = compose->autowrap;
3554 compose->autowrap = FALSE;
3556 text = GTK_TEXT_VIEW(compose->text);
3557 buffer = gtk_text_view_get_buffer(text);
3558 mark = gtk_text_buffer_get_insert(buffer);
3559 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3561 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3562 G_CALLBACK(text_inserted),
3565 cur_encoding = conv_get_locale_charset_str_no_utf8();
3567 file_contents = g_string_new("");
3568 while (fgets(buf, sizeof(buf), fp) != NULL) {
3571 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3572 str = g_strdup(buf);
3574 str = conv_codeset_strdup
3575 (buf, cur_encoding, CS_INTERNAL);
3578 /* strip <CR> if DOS/Windows file,
3579 replace <CR> with <LF> if Macintosh file. */
3582 if (len > 0 && str[len - 1] != '\n') {
3584 if (str[len] == '\r') str[len] = '\n';
3587 file_contents = g_string_append(file_contents, str);
3591 gtk_text_buffer_insert(buffer, &iter, file_contents->str, -1);
3592 g_string_free(file_contents, TRUE);
3594 compose_changed_cb(NULL, compose);
3595 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3596 G_CALLBACK(text_inserted),
3598 compose->autowrap = prev_autowrap;
3599 if (compose->autowrap)
3600 compose_wrap_all(compose);
3605 return COMPOSE_INSERT_INVALID_CHARACTER;
3607 return COMPOSE_INSERT_SUCCESS;
3610 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3611 const gchar *filename,
3612 const gchar *content_type,
3613 const gchar *charset)
3621 GtkListStore *store;
3623 gboolean has_binary = FALSE;
3625 if (!is_file_exist(file)) {
3626 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3627 gboolean result = FALSE;
3628 if (file_from_uri && is_file_exist(file_from_uri)) {
3629 result = compose_attach_append(
3630 compose, file_from_uri,
3631 filename, content_type,
3634 g_free(file_from_uri);
3637 alertpanel_error("File %s doesn't exist\n", filename);
3640 if ((size = get_file_size(file)) < 0) {
3641 alertpanel_error("Can't get file size of %s\n", filename);
3645 alertpanel_error(_("File %s is empty."), filename);
3648 if ((fp = g_fopen(file, "rb")) == NULL) {
3649 alertpanel_error(_("Can't read %s."), filename);
3654 ainfo = g_new0(AttachInfo, 1);
3655 auto_ainfo = g_auto_pointer_new_with_free
3656 (ainfo, (GFreeFunc) compose_attach_info_free);
3657 ainfo->file = g_strdup(file);
3660 ainfo->content_type = g_strdup(content_type);
3661 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3663 MsgFlags flags = {0, 0};
3665 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3666 ainfo->encoding = ENC_7BIT;
3668 ainfo->encoding = ENC_8BIT;
3670 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3671 if (msginfo && msginfo->subject)
3672 name = g_strdup(msginfo->subject);
3674 name = g_path_get_basename(filename ? filename : file);
3676 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3678 procmsg_msginfo_free(msginfo);
3680 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3681 ainfo->charset = g_strdup(charset);
3682 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3684 ainfo->encoding = ENC_BASE64;
3686 name = g_path_get_basename(filename ? filename : file);
3687 ainfo->name = g_strdup(name);
3691 ainfo->content_type = procmime_get_mime_type(file);
3692 if (!ainfo->content_type) {
3693 ainfo->content_type =
3694 g_strdup("application/octet-stream");
3695 ainfo->encoding = ENC_BASE64;
3696 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3698 procmime_get_encoding_for_text_file(file, &has_binary);
3700 ainfo->encoding = ENC_BASE64;
3701 name = g_path_get_basename(filename ? filename : file);
3702 ainfo->name = g_strdup(name);
3706 if (ainfo->name != NULL
3707 && !strcmp(ainfo->name, ".")) {
3708 g_free(ainfo->name);
3712 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3713 g_free(ainfo->content_type);
3714 ainfo->content_type = g_strdup("application/octet-stream");
3715 g_free(ainfo->charset);
3716 ainfo->charset = NULL;
3719 ainfo->size = (goffset)size;
3720 size_text = to_human_readable((goffset)size);
3722 store = GTK_LIST_STORE(gtk_tree_view_get_model
3723 (GTK_TREE_VIEW(compose->attach_clist)));
3725 gtk_list_store_append(store, &iter);
3726 gtk_list_store_set(store, &iter,
3727 COL_MIMETYPE, ainfo->content_type,
3728 COL_SIZE, size_text,
3729 COL_NAME, ainfo->name,
3730 COL_CHARSET, ainfo->charset,
3732 COL_AUTODATA, auto_ainfo,
3735 g_auto_pointer_free(auto_ainfo);
3736 compose_attach_update_label(compose);
3740 static void compose_use_signing(Compose *compose, gboolean use_signing)
3742 compose->use_signing = use_signing;
3743 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3746 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3748 compose->use_encryption = use_encryption;
3749 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3752 #define NEXT_PART_NOT_CHILD(info) \
3754 node = info->node; \
3755 while (node->children) \
3756 node = g_node_last_child(node); \
3757 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3760 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3764 MimeInfo *firsttext = NULL;
3765 MimeInfo *encrypted = NULL;
3768 const gchar *partname = NULL;
3770 mimeinfo = procmime_scan_message(msginfo);
3771 if (!mimeinfo) return;
3773 if (mimeinfo->node->children == NULL) {
3774 procmime_mimeinfo_free_all(mimeinfo);
3778 /* find first content part */
3779 child = (MimeInfo *) mimeinfo->node->children->data;
3780 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3781 child = (MimeInfo *)child->node->children->data;
3784 if (child->type == MIMETYPE_TEXT) {
3786 debug_print("First text part found\n");
3787 } else if (compose->mode == COMPOSE_REEDIT &&
3788 child->type == MIMETYPE_APPLICATION &&
3789 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3790 encrypted = (MimeInfo *)child->node->parent->data;
3793 child = (MimeInfo *) mimeinfo->node->children->data;
3794 while (child != NULL) {
3797 if (child == encrypted) {
3798 /* skip this part of tree */
3799 NEXT_PART_NOT_CHILD(child);
3803 if (child->type == MIMETYPE_MULTIPART) {
3804 /* get the actual content */
3805 child = procmime_mimeinfo_next(child);
3809 if (child == firsttext) {
3810 child = procmime_mimeinfo_next(child);
3814 outfile = procmime_get_tmp_file_name(child);
3815 if ((err = procmime_get_part(outfile, child)) < 0)
3816 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3818 gchar *content_type;
3820 content_type = procmime_get_content_type_str(child->type, child->subtype);
3822 /* if we meet a pgp signature, we don't attach it, but
3823 * we force signing. */
3824 if ((strcmp(content_type, "application/pgp-signature") &&
3825 strcmp(content_type, "application/pkcs7-signature") &&
3826 strcmp(content_type, "application/x-pkcs7-signature"))
3827 || compose->mode == COMPOSE_REDIRECT) {
3828 partname = procmime_mimeinfo_get_parameter(child, "filename");
3829 if (partname == NULL)
3830 partname = procmime_mimeinfo_get_parameter(child, "name");
3831 if (partname == NULL)
3833 compose_attach_append(compose, outfile,
3834 partname, content_type,
3835 procmime_mimeinfo_get_parameter(child, "charset"));
3837 compose_force_signing(compose, compose->account, NULL);
3839 g_free(content_type);
3842 NEXT_PART_NOT_CHILD(child);
3844 procmime_mimeinfo_free_all(mimeinfo);
3847 #undef NEXT_PART_NOT_CHILD
3852 WAIT_FOR_INDENT_CHAR,
3853 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3856 /* return indent length, we allow:
3857 indent characters followed by indent characters or spaces/tabs,
3858 alphabets and numbers immediately followed by indent characters,
3859 and the repeating sequences of the above
3860 If quote ends with multiple spaces, only the first one is included. */
3861 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3862 const GtkTextIter *start, gint *len)
3864 GtkTextIter iter = *start;
3868 IndentState state = WAIT_FOR_INDENT_CHAR;
3871 gint alnum_count = 0;
3872 gint space_count = 0;
3875 if (prefs_common.quote_chars == NULL) {
3879 while (!gtk_text_iter_ends_line(&iter)) {
3880 wc = gtk_text_iter_get_char(&iter);
3881 if (g_unichar_iswide(wc))
3883 clen = g_unichar_to_utf8(wc, ch);
3887 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3888 is_space = g_unichar_isspace(wc);
3890 if (state == WAIT_FOR_INDENT_CHAR) {
3891 if (!is_indent && !g_unichar_isalnum(wc))
3894 quote_len += alnum_count + space_count + 1;
3895 alnum_count = space_count = 0;
3896 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3899 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3900 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3904 else if (is_indent) {
3905 quote_len += alnum_count + space_count + 1;
3906 alnum_count = space_count = 0;
3909 state = WAIT_FOR_INDENT_CHAR;
3913 gtk_text_iter_forward_char(&iter);
3916 if (quote_len > 0 && space_count > 0)
3922 if (quote_len > 0) {
3924 gtk_text_iter_forward_chars(&iter, quote_len);
3925 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3931 /* return >0 if the line is itemized */
3932 static int compose_itemized_length(GtkTextBuffer *buffer,
3933 const GtkTextIter *start)
3935 GtkTextIter iter = *start;
3940 if (gtk_text_iter_ends_line(&iter))
3945 wc = gtk_text_iter_get_char(&iter);
3946 if (!g_unichar_isspace(wc))
3948 gtk_text_iter_forward_char(&iter);
3949 if (gtk_text_iter_ends_line(&iter))
3953 clen = g_unichar_to_utf8(wc, ch);
3957 if (!strchr("*-+", ch[0]))
3960 gtk_text_iter_forward_char(&iter);
3961 if (gtk_text_iter_ends_line(&iter))
3963 wc = gtk_text_iter_get_char(&iter);
3964 if (g_unichar_isspace(wc)) {
3970 /* return the string at the start of the itemization */
3971 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3972 const GtkTextIter *start)
3974 GtkTextIter iter = *start;
3977 GString *item_chars = g_string_new("");
3980 if (gtk_text_iter_ends_line(&iter))
3985 wc = gtk_text_iter_get_char(&iter);
3986 if (!g_unichar_isspace(wc))
3988 gtk_text_iter_forward_char(&iter);
3989 if (gtk_text_iter_ends_line(&iter))
3991 g_string_append_unichar(item_chars, wc);
3994 str = item_chars->str;
3995 g_string_free(item_chars, FALSE);
3999 /* return the number of spaces at a line's start */
4000 static int compose_left_offset_length(GtkTextBuffer *buffer,
4001 const GtkTextIter *start)
4003 GtkTextIter iter = *start;
4006 if (gtk_text_iter_ends_line(&iter))
4010 wc = gtk_text_iter_get_char(&iter);
4011 if (!g_unichar_isspace(wc))
4014 gtk_text_iter_forward_char(&iter);
4015 if (gtk_text_iter_ends_line(&iter))
4019 gtk_text_iter_forward_char(&iter);
4020 if (gtk_text_iter_ends_line(&iter))
4025 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4026 const GtkTextIter *start,
4027 GtkTextIter *break_pos,
4031 GtkTextIter iter = *start, line_end = *start;
4032 PangoLogAttr *attrs;
4039 gboolean can_break = FALSE;
4040 gboolean do_break = FALSE;
4041 gboolean was_white = FALSE;
4042 gboolean prev_dont_break = FALSE;
4044 gtk_text_iter_forward_to_line_end(&line_end);
4045 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4046 len = g_utf8_strlen(str, -1);
4050 g_warning("compose_get_line_break_pos: len = 0!\n");
4054 /* g_print("breaking line: %d: %s (len = %d)\n",
4055 gtk_text_iter_get_line(&iter), str, len); */
4057 attrs = g_new(PangoLogAttr, len + 1);
4059 pango_default_break(str, -1, NULL, attrs, len + 1);
4063 /* skip quote and leading spaces */
4064 for (i = 0; *p != '\0' && i < len; i++) {
4067 wc = g_utf8_get_char(p);
4068 if (i >= quote_len && !g_unichar_isspace(wc))
4070 if (g_unichar_iswide(wc))
4072 else if (*p == '\t')
4076 p = g_utf8_next_char(p);
4079 for (; *p != '\0' && i < len; i++) {
4080 PangoLogAttr *attr = attrs + i;
4084 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4087 was_white = attr->is_white;
4089 /* don't wrap URI */
4090 if ((uri_len = get_uri_len(p)) > 0) {
4092 if (pos > 0 && col > max_col) {
4102 wc = g_utf8_get_char(p);
4103 if (g_unichar_iswide(wc)) {
4105 if (prev_dont_break && can_break && attr->is_line_break)
4107 } else if (*p == '\t')
4111 if (pos > 0 && col > max_col) {
4116 if (*p == '-' || *p == '/')
4117 prev_dont_break = TRUE;
4119 prev_dont_break = FALSE;
4121 p = g_utf8_next_char(p);
4125 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4130 *break_pos = *start;
4131 gtk_text_iter_set_line_offset(break_pos, pos);
4136 static gboolean compose_join_next_line(Compose *compose,
4137 GtkTextBuffer *buffer,
4139 const gchar *quote_str)
4141 GtkTextIter iter_ = *iter, cur, prev, next, end;
4142 PangoLogAttr attrs[3];
4144 gchar *next_quote_str;
4147 gboolean keep_cursor = FALSE;
4149 if (!gtk_text_iter_forward_line(&iter_) ||
4150 gtk_text_iter_ends_line(&iter_)) {
4153 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4155 if ((quote_str || next_quote_str) &&
4156 strcmp2(quote_str, next_quote_str) != 0) {
4157 g_free(next_quote_str);
4160 g_free(next_quote_str);
4163 if (quote_len > 0) {
4164 gtk_text_iter_forward_chars(&end, quote_len);
4165 if (gtk_text_iter_ends_line(&end)) {
4170 /* don't join itemized lines */
4171 if (compose_itemized_length(buffer, &end) > 0) {
4175 /* don't join signature separator */
4176 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4179 /* delete quote str */
4181 gtk_text_buffer_delete(buffer, &iter_, &end);
4183 /* don't join line breaks put by the user */
4185 gtk_text_iter_backward_char(&cur);
4186 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4187 gtk_text_iter_forward_char(&cur);
4191 gtk_text_iter_forward_char(&cur);
4192 /* delete linebreak and extra spaces */
4193 while (gtk_text_iter_backward_char(&cur)) {
4194 wc1 = gtk_text_iter_get_char(&cur);
4195 if (!g_unichar_isspace(wc1))
4200 while (!gtk_text_iter_ends_line(&cur)) {
4201 wc1 = gtk_text_iter_get_char(&cur);
4202 if (!g_unichar_isspace(wc1))
4204 gtk_text_iter_forward_char(&cur);
4207 if (!gtk_text_iter_equal(&prev, &next)) {
4210 mark = gtk_text_buffer_get_insert(buffer);
4211 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4212 if (gtk_text_iter_equal(&prev, &cur))
4214 gtk_text_buffer_delete(buffer, &prev, &next);
4218 /* insert space if required */
4219 gtk_text_iter_backward_char(&prev);
4220 wc1 = gtk_text_iter_get_char(&prev);
4221 wc2 = gtk_text_iter_get_char(&next);
4222 gtk_text_iter_forward_char(&next);
4223 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4224 pango_default_break(str, -1, NULL, attrs, 3);
4225 if (!attrs[1].is_line_break ||
4226 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4227 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4229 gtk_text_iter_backward_char(&iter_);
4230 gtk_text_buffer_place_cursor(buffer, &iter_);
4239 #define ADD_TXT_POS(bp_, ep_, pti_) \
4240 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4241 last = last->next; \
4242 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4243 last->next = NULL; \
4245 g_warning("alloc error scanning URIs\n"); \
4248 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4250 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4251 GtkTextBuffer *buffer;
4252 GtkTextIter iter, break_pos, end_of_line;
4253 gchar *quote_str = NULL;
4255 gboolean wrap_quote = prefs_common.linewrap_quote;
4256 gboolean prev_autowrap = compose->autowrap;
4257 gint startq_offset = -1, noq_offset = -1;
4258 gint uri_start = -1, uri_stop = -1;
4259 gint nouri_start = -1, nouri_stop = -1;
4260 gint num_blocks = 0;
4261 gint quotelevel = -1;
4262 gboolean modified = force;
4263 gboolean removed = FALSE;
4264 gboolean modified_before_remove = FALSE;
4266 gboolean start = TRUE;
4267 gint itemized_len = 0, rem_item_len = 0;
4268 gchar *itemized_chars = NULL;
4269 gboolean item_continuation = FALSE;
4274 if (compose->draft_timeout_tag == -2) {
4278 compose->autowrap = FALSE;
4280 buffer = gtk_text_view_get_buffer(text);
4281 undo_wrapping(compose->undostruct, TRUE);
4286 mark = gtk_text_buffer_get_insert(buffer);
4287 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4291 if (compose->draft_timeout_tag == -2) {
4292 if (gtk_text_iter_ends_line(&iter)) {
4293 while (gtk_text_iter_ends_line(&iter) &&
4294 gtk_text_iter_forward_line(&iter))
4297 while (gtk_text_iter_backward_line(&iter)) {
4298 if (gtk_text_iter_ends_line(&iter)) {
4299 gtk_text_iter_forward_line(&iter);
4305 /* move to line start */
4306 gtk_text_iter_set_line_offset(&iter, 0);
4309 itemized_len = compose_itemized_length(buffer, &iter);
4311 if (!itemized_len) {
4312 itemized_len = compose_left_offset_length(buffer, &iter);
4313 item_continuation = TRUE;
4317 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4319 /* go until paragraph end (empty line) */
4320 while (start || !gtk_text_iter_ends_line(&iter)) {
4321 gchar *scanpos = NULL;
4322 /* parse table - in order of priority */
4324 const gchar *needle; /* token */
4326 /* token search function */
4327 gchar *(*search) (const gchar *haystack,
4328 const gchar *needle);
4329 /* part parsing function */
4330 gboolean (*parse) (const gchar *start,
4331 const gchar *scanpos,
4335 /* part to URI function */
4336 gchar *(*build_uri) (const gchar *bp,
4340 static struct table parser[] = {
4341 {"http://", strcasestr, get_uri_part, make_uri_string},
4342 {"https://", strcasestr, get_uri_part, make_uri_string},
4343 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4344 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4345 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4346 {"www.", strcasestr, get_uri_part, make_http_string},
4347 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4348 {"@", strcasestr, get_email_part, make_email_string}
4350 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4351 gint last_index = PARSE_ELEMS;
4353 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4357 if (!prev_autowrap && num_blocks == 0) {
4359 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4360 G_CALLBACK(text_inserted),
4363 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4366 uri_start = uri_stop = -1;
4368 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4371 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4372 if (startq_offset == -1)
4373 startq_offset = gtk_text_iter_get_offset(&iter);
4374 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4375 if (quotelevel > 2) {
4376 /* recycle colors */
4377 if (prefs_common.recycle_quote_colors)
4386 if (startq_offset == -1)
4387 noq_offset = gtk_text_iter_get_offset(&iter);
4391 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4394 if (gtk_text_iter_ends_line(&iter)) {
4396 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4397 prefs_common.linewrap_len,
4399 GtkTextIter prev, next, cur;
4400 if (prev_autowrap != FALSE || force) {
4401 compose->automatic_break = TRUE;
4403 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4404 compose->automatic_break = FALSE;
4405 if (itemized_len && compose->autoindent) {
4406 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4407 if (!item_continuation)
4408 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4410 } else if (quote_str && wrap_quote) {
4411 compose->automatic_break = TRUE;
4413 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4414 compose->automatic_break = FALSE;
4415 if (itemized_len && compose->autoindent) {
4416 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4417 if (!item_continuation)
4418 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4422 /* remove trailing spaces */
4424 rem_item_len = itemized_len;
4425 while (compose->autoindent && rem_item_len-- > 0)
4426 gtk_text_iter_backward_char(&cur);
4427 gtk_text_iter_backward_char(&cur);
4430 while (!gtk_text_iter_starts_line(&cur)) {
4433 gtk_text_iter_backward_char(&cur);
4434 wc = gtk_text_iter_get_char(&cur);
4435 if (!g_unichar_isspace(wc))
4439 if (!gtk_text_iter_equal(&prev, &next)) {
4440 gtk_text_buffer_delete(buffer, &prev, &next);
4442 gtk_text_iter_forward_char(&break_pos);
4446 gtk_text_buffer_insert(buffer, &break_pos,
4450 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4452 /* move iter to current line start */
4453 gtk_text_iter_set_line_offset(&iter, 0);
4460 /* move iter to next line start */
4466 if (!prev_autowrap && num_blocks > 0) {
4468 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4469 G_CALLBACK(text_inserted),
4473 while (!gtk_text_iter_ends_line(&end_of_line)) {
4474 gtk_text_iter_forward_char(&end_of_line);
4476 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4478 nouri_start = gtk_text_iter_get_offset(&iter);
4479 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4481 walk_pos = gtk_text_iter_get_offset(&iter);
4482 /* FIXME: this looks phony. scanning for anything in the parse table */
4483 for (n = 0; n < PARSE_ELEMS; n++) {
4486 tmp = parser[n].search(walk, parser[n].needle);
4488 if (scanpos == NULL || tmp < scanpos) {
4497 /* check if URI can be parsed */
4498 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4499 (const gchar **)&ep, FALSE)
4500 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4504 strlen(parser[last_index].needle);
4507 uri_start = walk_pos + (bp - o_walk);
4508 uri_stop = walk_pos + (ep - o_walk);
4512 gtk_text_iter_forward_line(&iter);
4515 if (startq_offset != -1) {
4516 GtkTextIter startquote, endquote;
4517 gtk_text_buffer_get_iter_at_offset(
4518 buffer, &startquote, startq_offset);
4521 switch (quotelevel) {
4523 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4524 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4525 gtk_text_buffer_apply_tag_by_name(
4526 buffer, "quote0", &startquote, &endquote);
4527 gtk_text_buffer_remove_tag_by_name(
4528 buffer, "quote1", &startquote, &endquote);
4529 gtk_text_buffer_remove_tag_by_name(
4530 buffer, "quote2", &startquote, &endquote);
4535 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4536 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4537 gtk_text_buffer_apply_tag_by_name(
4538 buffer, "quote1", &startquote, &endquote);
4539 gtk_text_buffer_remove_tag_by_name(
4540 buffer, "quote0", &startquote, &endquote);
4541 gtk_text_buffer_remove_tag_by_name(
4542 buffer, "quote2", &startquote, &endquote);
4547 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4548 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4549 gtk_text_buffer_apply_tag_by_name(
4550 buffer, "quote2", &startquote, &endquote);
4551 gtk_text_buffer_remove_tag_by_name(
4552 buffer, "quote0", &startquote, &endquote);
4553 gtk_text_buffer_remove_tag_by_name(
4554 buffer, "quote1", &startquote, &endquote);
4560 } else if (noq_offset != -1) {
4561 GtkTextIter startnoquote, endnoquote;
4562 gtk_text_buffer_get_iter_at_offset(
4563 buffer, &startnoquote, noq_offset);
4566 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4567 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4568 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4569 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4570 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4571 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4572 gtk_text_buffer_remove_tag_by_name(
4573 buffer, "quote0", &startnoquote, &endnoquote);
4574 gtk_text_buffer_remove_tag_by_name(
4575 buffer, "quote1", &startnoquote, &endnoquote);
4576 gtk_text_buffer_remove_tag_by_name(
4577 buffer, "quote2", &startnoquote, &endnoquote);
4583 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4584 GtkTextIter nouri_start_iter, nouri_end_iter;
4585 gtk_text_buffer_get_iter_at_offset(
4586 buffer, &nouri_start_iter, nouri_start);
4587 gtk_text_buffer_get_iter_at_offset(
4588 buffer, &nouri_end_iter, nouri_stop);
4589 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4590 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4591 gtk_text_buffer_remove_tag_by_name(
4592 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4593 modified_before_remove = modified;
4598 if (uri_start >= 0 && uri_stop > 0) {
4599 GtkTextIter uri_start_iter, uri_end_iter, back;
4600 gtk_text_buffer_get_iter_at_offset(
4601 buffer, &uri_start_iter, uri_start);
4602 gtk_text_buffer_get_iter_at_offset(
4603 buffer, &uri_end_iter, uri_stop);
4604 back = uri_end_iter;
4605 gtk_text_iter_backward_char(&back);
4606 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4607 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4608 gtk_text_buffer_apply_tag_by_name(
4609 buffer, "link", &uri_start_iter, &uri_end_iter);
4611 if (removed && !modified_before_remove) {
4617 // debug_print("not modified, out after %d lines\n", lines);
4621 // debug_print("modified, out after %d lines\n", lines);
4623 g_free(itemized_chars);
4626 undo_wrapping(compose->undostruct, FALSE);
4627 compose->autowrap = prev_autowrap;
4632 void compose_action_cb(void *data)
4634 Compose *compose = (Compose *)data;
4635 compose_wrap_all(compose);
4638 static void compose_wrap_all(Compose *compose)
4640 compose_wrap_all_full(compose, FALSE);
4643 static void compose_wrap_all_full(Compose *compose, gboolean force)
4645 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4646 GtkTextBuffer *buffer;
4648 gboolean modified = TRUE;
4650 buffer = gtk_text_view_get_buffer(text);
4652 gtk_text_buffer_get_start_iter(buffer, &iter);
4653 while (!gtk_text_iter_is_end(&iter) && modified)
4654 modified = compose_beautify_paragraph(compose, &iter, force);
4658 static void compose_set_title(Compose *compose)
4664 edited = compose->modified ? _(" [Edited]") : "";
4666 subject = gtk_editable_get_chars(
4667 GTK_EDITABLE(compose->subject_entry), 0, -1);
4669 #ifndef GENERIC_UMPC
4670 if (subject && strlen(subject))
4671 str = g_strdup_printf(_("%s - Compose message%s"),
4674 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4676 str = g_strdup(_("Compose message"));
4679 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4685 * compose_current_mail_account:
4687 * Find a current mail account (the currently selected account, or the
4688 * default account, if a news account is currently selected). If a
4689 * mail account cannot be found, display an error message.
4691 * Return value: Mail account, or NULL if not found.
4693 static PrefsAccount *
4694 compose_current_mail_account(void)
4698 if (cur_account && cur_account->protocol != A_NNTP)
4701 ac = account_get_default();
4702 if (!ac || ac->protocol == A_NNTP) {
4703 alertpanel_error(_("Account for sending mail is not specified.\n"
4704 "Please select a mail account before sending."));
4711 #define QUOTE_IF_REQUIRED(out, str) \
4713 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4717 len = strlen(str) + 3; \
4718 if ((__tmp = alloca(len)) == NULL) { \
4719 g_warning("can't allocate memory\n"); \
4720 g_string_free(header, TRUE); \
4723 g_snprintf(__tmp, len, "\"%s\"", str); \
4728 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4729 g_warning("can't allocate memory\n"); \
4730 g_string_free(header, TRUE); \
4733 strcpy(__tmp, str); \
4739 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4741 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4745 len = strlen(str) + 3; \
4746 if ((__tmp = alloca(len)) == NULL) { \
4747 g_warning("can't allocate memory\n"); \
4750 g_snprintf(__tmp, len, "\"%s\"", str); \
4755 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4756 g_warning("can't allocate memory\n"); \
4759 strcpy(__tmp, str); \
4765 static void compose_select_account(Compose *compose, PrefsAccount *account,
4768 gchar *from = NULL, *header = NULL;
4769 ComposeHeaderEntry *header_entry;
4770 #if GTK_CHECK_VERSION(2, 24, 0)
4774 cm_return_if_fail(account != NULL);
4776 compose->account = account;
4777 if (account->name && *account->name) {
4779 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4780 from = g_strdup_printf("%s <%s>",
4781 buf, account->address);
4782 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4784 from = g_strdup_printf("<%s>",
4786 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4791 compose_set_title(compose);
4793 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4794 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4796 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4797 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4798 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4800 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4802 activate_privacy_system(compose, account, FALSE);
4804 if (!init && compose->mode != COMPOSE_REDIRECT) {
4805 undo_block(compose->undostruct);
4806 compose_insert_sig(compose, TRUE);
4807 undo_unblock(compose->undostruct);
4810 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4811 #if !GTK_CHECK_VERSION(2, 24, 0)
4812 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4814 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4815 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4816 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4819 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4820 if (account->protocol == A_NNTP) {
4821 if (!strcmp(header, _("To:")))
4822 combobox_select_by_text(
4823 GTK_COMBO_BOX(header_entry->combo),
4826 if (!strcmp(header, _("Newsgroups:")))
4827 combobox_select_by_text(
4828 GTK_COMBO_BOX(header_entry->combo),
4836 /* use account's dict info if set */
4837 if (compose->gtkaspell) {
4838 if (account->enable_default_dictionary)
4839 gtkaspell_change_dict(compose->gtkaspell,
4840 account->default_dictionary, FALSE);
4841 if (account->enable_default_alt_dictionary)
4842 gtkaspell_change_alt_dict(compose->gtkaspell,
4843 account->default_alt_dictionary);
4844 if (account->enable_default_dictionary
4845 || account->enable_default_alt_dictionary)
4846 compose_spell_menu_changed(compose);
4851 gboolean compose_check_for_valid_recipient(Compose *compose) {
4852 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4853 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4854 gboolean recipient_found = FALSE;
4858 /* free to and newsgroup list */
4859 slist_free_strings_full(compose->to_list);
4860 compose->to_list = NULL;
4862 slist_free_strings_full(compose->newsgroup_list);
4863 compose->newsgroup_list = NULL;
4865 /* search header entries for to and newsgroup entries */
4866 for (list = compose->header_list; list; list = list->next) {
4869 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4870 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4873 if (entry[0] != '\0') {
4874 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4875 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4876 compose->to_list = address_list_append(compose->to_list, entry);
4877 recipient_found = TRUE;
4880 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4881 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4882 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4883 recipient_found = TRUE;
4890 return recipient_found;
4893 static gboolean compose_check_for_set_recipients(Compose *compose)
4895 if (compose->account->set_autocc && compose->account->auto_cc) {
4896 gboolean found_other = FALSE;
4898 /* search header entries for to and newsgroup entries */
4899 for (list = compose->header_list; list; list = list->next) {
4902 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4903 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4906 if (strcmp(entry, compose->account->auto_cc)
4907 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4917 if (compose->batch) {
4918 gtk_widget_show_all(compose->window);
4920 aval = alertpanel(_("Send"),
4921 _("The only recipient is the default CC address. Send anyway?"),
4922 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4923 if (aval != G_ALERTALTERNATE)
4927 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4928 gboolean found_other = FALSE;
4930 /* search header entries for to and newsgroup entries */
4931 for (list = compose->header_list; list; list = list->next) {
4934 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4935 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4938 if (strcmp(entry, compose->account->auto_bcc)
4939 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4949 if (compose->batch) {
4950 gtk_widget_show_all(compose->window);
4952 aval = alertpanel(_("Send"),
4953 _("The only recipient is the default BCC address. Send anyway?"),
4954 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4955 if (aval != G_ALERTALTERNATE)
4962 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4966 if (compose_check_for_valid_recipient(compose) == FALSE) {
4967 if (compose->batch) {
4968 gtk_widget_show_all(compose->window);
4970 alertpanel_error(_("Recipient is not specified."));
4974 if (compose_check_for_set_recipients(compose) == FALSE) {
4978 if (!compose->batch) {
4979 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4980 if (*str == '\0' && check_everything == TRUE &&
4981 compose->mode != COMPOSE_REDIRECT) {
4983 gchar *button_label;
4986 if (compose->sending)
4987 button_label = _("+_Send");
4989 button_label = _("+_Queue");
4990 message = g_strdup_printf(_("Subject is empty. %s"),
4991 compose->sending?_("Send it anyway?"):
4992 _("Queue it anyway?"));
4994 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4995 GTK_STOCK_CANCEL, button_label, NULL);
4997 if (aval != G_ALERTALTERNATE)
5002 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5008 gint compose_send(Compose *compose)
5011 FolderItem *folder = NULL;
5013 gchar *msgpath = NULL;
5014 gboolean discard_window = FALSE;
5015 gchar *errstr = NULL;
5016 gchar *tmsgid = NULL;
5017 MainWindow *mainwin = mainwindow_get_mainwindow();
5018 gboolean queued_removed = FALSE;
5020 if (prefs_common.send_dialog_invisible
5021 || compose->batch == TRUE)
5022 discard_window = TRUE;
5024 compose_allow_user_actions (compose, FALSE);
5025 compose->sending = TRUE;
5027 if (compose_check_entries(compose, TRUE) == FALSE) {
5028 if (compose->batch) {
5029 gtk_widget_show_all(compose->window);
5035 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5038 if (compose->batch) {
5039 gtk_widget_show_all(compose->window);
5042 alertpanel_error(_("Could not queue message for sending:\n\n"
5043 "Charset conversion failed."));
5044 } else if (val == -5) {
5045 alertpanel_error(_("Could not queue message for sending:\n\n"
5046 "Couldn't get recipient encryption key."));
5047 } else if (val == -6) {
5049 } else if (val == -3) {
5050 if (privacy_peek_error())
5051 alertpanel_error(_("Could not queue message for sending:\n\n"
5052 "Signature failed: %s"), privacy_get_error());
5053 } else if (val == -2 && errno != 0) {
5054 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5056 alertpanel_error(_("Could not queue message for sending."));
5061 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5062 if (discard_window) {
5063 compose->sending = FALSE;
5064 compose_close(compose);
5065 /* No more compose access in the normal codepath
5066 * after this point! */
5071 alertpanel_error(_("The message was queued but could not be "
5072 "sent.\nUse \"Send queued messages\" from "
5073 "the main window to retry."));
5074 if (!discard_window) {
5081 if (msgpath == NULL) {
5082 msgpath = folder_item_fetch_msg(folder, msgnum);
5083 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5086 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5087 claws_unlink(msgpath);
5090 if (!discard_window) {
5092 if (!queued_removed)
5093 folder_item_remove_msg(folder, msgnum);
5094 folder_item_scan(folder);
5096 /* make sure we delete that */
5097 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5099 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5100 folder_item_remove_msg(folder, tmp->msgnum);
5101 procmsg_msginfo_free(tmp);
5108 if (!queued_removed)
5109 folder_item_remove_msg(folder, msgnum);
5110 folder_item_scan(folder);
5112 /* make sure we delete that */
5113 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5115 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5116 folder_item_remove_msg(folder, tmp->msgnum);
5117 procmsg_msginfo_free(tmp);
5120 if (!discard_window) {
5121 compose->sending = FALSE;
5122 compose_allow_user_actions (compose, TRUE);
5123 compose_close(compose);
5127 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5128 "the main window to retry."), errstr);
5131 alertpanel_error_log(_("The message was queued but could not be "
5132 "sent.\nUse \"Send queued messages\" from "
5133 "the main window to retry."));
5135 if (!discard_window) {
5144 toolbar_main_set_sensitive(mainwin);
5145 main_window_set_menu_sensitive(mainwin);
5151 compose_allow_user_actions (compose, TRUE);
5152 compose->sending = FALSE;
5153 compose->modified = TRUE;
5154 toolbar_main_set_sensitive(mainwin);
5155 main_window_set_menu_sensitive(mainwin);
5160 static gboolean compose_use_attach(Compose *compose)
5162 GtkTreeModel *model = gtk_tree_view_get_model
5163 (GTK_TREE_VIEW(compose->attach_clist));
5164 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5167 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5170 gchar buf[BUFFSIZE];
5172 gboolean first_to_address;
5173 gboolean first_cc_address;
5175 ComposeHeaderEntry *headerentry;
5176 const gchar *headerentryname;
5177 const gchar *cc_hdr;
5178 const gchar *to_hdr;
5179 gboolean err = FALSE;
5181 debug_print("Writing redirect header\n");
5183 cc_hdr = prefs_common_translated_header_name("Cc:");
5184 to_hdr = prefs_common_translated_header_name("To:");
5186 first_to_address = TRUE;
5187 for (list = compose->header_list; list; list = list->next) {
5188 headerentry = ((ComposeHeaderEntry *)list->data);
5189 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5191 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5192 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5193 Xstrdup_a(str, entstr, return -1);
5195 if (str[0] != '\0') {
5196 compose_convert_header
5197 (compose, buf, sizeof(buf), str,
5198 strlen("Resent-To") + 2, TRUE);
5200 if (first_to_address) {
5201 err |= (fprintf(fp, "Resent-To: ") < 0);
5202 first_to_address = FALSE;
5204 err |= (fprintf(fp, ",") < 0);
5206 err |= (fprintf(fp, "%s", buf) < 0);
5210 if (!first_to_address) {
5211 err |= (fprintf(fp, "\n") < 0);
5214 first_cc_address = TRUE;
5215 for (list = compose->header_list; list; list = list->next) {
5216 headerentry = ((ComposeHeaderEntry *)list->data);
5217 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5219 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5220 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5221 Xstrdup_a(str, strg, return -1);
5223 if (str[0] != '\0') {
5224 compose_convert_header
5225 (compose, buf, sizeof(buf), str,
5226 strlen("Resent-Cc") + 2, TRUE);
5228 if (first_cc_address) {
5229 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5230 first_cc_address = FALSE;
5232 err |= (fprintf(fp, ",") < 0);
5234 err |= (fprintf(fp, "%s", buf) < 0);
5238 if (!first_cc_address) {
5239 err |= (fprintf(fp, "\n") < 0);
5242 return (err ? -1:0);
5245 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5247 gchar buf[BUFFSIZE];
5249 const gchar *entstr;
5250 /* struct utsname utsbuf; */
5251 gboolean err = FALSE;
5253 cm_return_val_if_fail(fp != NULL, -1);
5254 cm_return_val_if_fail(compose->account != NULL, -1);
5255 cm_return_val_if_fail(compose->account->address != NULL, -1);
5258 get_rfc822_date(buf, sizeof(buf));
5259 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5262 if (compose->account->name && *compose->account->name) {
5263 compose_convert_header
5264 (compose, buf, sizeof(buf), compose->account->name,
5265 strlen("From: "), TRUE);
5266 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5267 buf, compose->account->address) < 0);
5269 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5272 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5273 if (*entstr != '\0') {
5274 Xstrdup_a(str, entstr, return -1);
5277 compose_convert_header(compose, buf, sizeof(buf), str,
5278 strlen("Subject: "), FALSE);
5279 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5283 /* Resent-Message-ID */
5284 if (compose->account->set_domain && compose->account->domain) {
5285 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5286 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5287 g_snprintf(buf, sizeof(buf), "%s",
5288 strchr(compose->account->address, '@') ?
5289 strchr(compose->account->address, '@')+1 :
5290 compose->account->address);
5292 g_snprintf(buf, sizeof(buf), "%s", "");
5295 if (compose->account->gen_msgid) {
5297 if (compose->account->msgid_with_addr) {
5298 addr = compose->account->address;
5300 generate_msgid(buf, sizeof(buf), addr);
5301 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5302 compose->msgid = g_strdup(buf);
5304 compose->msgid = NULL;
5307 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5310 /* separator between header and body */
5311 err |= (fputs("\n", fp) == EOF);
5313 return (err ? -1:0);
5316 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5320 gchar buf[BUFFSIZE];
5322 gboolean skip = FALSE;
5323 gboolean err = FALSE;
5324 gchar *not_included[]={
5325 "Return-Path:", "Delivered-To:", "Received:",
5326 "Subject:", "X-UIDL:", "AF:",
5327 "NF:", "PS:", "SRH:",
5328 "SFN:", "DSR:", "MID:",
5329 "CFG:", "PT:", "S:",
5330 "RQ:", "SSV:", "NSV:",
5331 "SSH:", "R:", "MAID:",
5332 "NAID:", "RMID:", "FMID:",
5333 "SCF:", "RRCPT:", "NG:",
5334 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5335 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5336 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5337 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5338 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5341 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5342 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5346 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5348 for (i = 0; not_included[i] != NULL; i++) {
5349 if (g_ascii_strncasecmp(buf, not_included[i],
5350 strlen(not_included[i])) == 0) {
5357 if (fputs(buf, fdest) == -1)
5360 if (!prefs_common.redirect_keep_from) {
5361 if (g_ascii_strncasecmp(buf, "From:",
5362 strlen("From:")) == 0) {
5363 err |= (fputs(" (by way of ", fdest) == EOF);
5364 if (compose->account->name
5365 && *compose->account->name) {
5366 compose_convert_header
5367 (compose, buf, sizeof(buf),
5368 compose->account->name,
5371 err |= (fprintf(fdest, "%s <%s>",
5373 compose->account->address) < 0);
5375 err |= (fprintf(fdest, "%s",
5376 compose->account->address) < 0);
5377 err |= (fputs(")", fdest) == EOF);
5381 if (fputs("\n", fdest) == -1)
5388 if (compose_redirect_write_headers(compose, fdest))
5391 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5392 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5405 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5407 GtkTextBuffer *buffer;
5408 GtkTextIter start, end;
5411 const gchar *out_codeset;
5412 EncodingType encoding = ENC_UNKNOWN;
5413 MimeInfo *mimemsg, *mimetext;
5415 const gchar *src_codeset = CS_INTERNAL;
5416 gchar *from_addr = NULL;
5417 gchar *from_name = NULL;
5419 if (action == COMPOSE_WRITE_FOR_SEND)
5420 attach_parts = TRUE;
5422 /* create message MimeInfo */
5423 mimemsg = procmime_mimeinfo_new();
5424 mimemsg->type = MIMETYPE_MESSAGE;
5425 mimemsg->subtype = g_strdup("rfc822");
5426 mimemsg->content = MIMECONTENT_MEM;
5427 mimemsg->tmp = TRUE; /* must free content later */
5428 mimemsg->data.mem = compose_get_header(compose);
5430 /* Create text part MimeInfo */
5431 /* get all composed text */
5432 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5433 gtk_text_buffer_get_start_iter(buffer, &start);
5434 gtk_text_buffer_get_end_iter(buffer, &end);
5435 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5437 out_codeset = conv_get_charset_str(compose->out_encoding);
5439 if (!out_codeset && is_ascii_str(chars)) {
5440 out_codeset = CS_US_ASCII;
5441 } else if (prefs_common.outgoing_fallback_to_ascii &&
5442 is_ascii_str(chars)) {
5443 out_codeset = CS_US_ASCII;
5444 encoding = ENC_7BIT;
5448 gchar *test_conv_global_out = NULL;
5449 gchar *test_conv_reply = NULL;
5451 /* automatic mode. be automatic. */
5452 codeconv_set_strict(TRUE);
5454 out_codeset = conv_get_outgoing_charset_str();
5456 debug_print("trying to convert to %s\n", out_codeset);
5457 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5460 if (!test_conv_global_out && compose->orig_charset
5461 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5462 out_codeset = compose->orig_charset;
5463 debug_print("failure; trying to convert to %s\n", out_codeset);
5464 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5467 if (!test_conv_global_out && !test_conv_reply) {
5469 out_codeset = CS_INTERNAL;
5470 debug_print("failure; finally using %s\n", out_codeset);
5472 g_free(test_conv_global_out);
5473 g_free(test_conv_reply);
5474 codeconv_set_strict(FALSE);
5477 if (encoding == ENC_UNKNOWN) {
5478 if (prefs_common.encoding_method == CTE_BASE64)
5479 encoding = ENC_BASE64;
5480 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5481 encoding = ENC_QUOTED_PRINTABLE;
5482 else if (prefs_common.encoding_method == CTE_8BIT)
5483 encoding = ENC_8BIT;
5485 encoding = procmime_get_encoding_for_charset(out_codeset);
5488 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5489 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5491 if (action == COMPOSE_WRITE_FOR_SEND) {
5492 codeconv_set_strict(TRUE);
5493 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5494 codeconv_set_strict(FALSE);
5500 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5501 "to the specified %s charset.\n"
5502 "Send it as %s?"), out_codeset, src_codeset);
5503 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5504 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5507 if (aval != G_ALERTALTERNATE) {
5512 out_codeset = src_codeset;
5518 out_codeset = src_codeset;
5523 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5524 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5525 strstr(buf, "\nFrom ") != NULL) {
5526 encoding = ENC_QUOTED_PRINTABLE;
5530 mimetext = procmime_mimeinfo_new();
5531 mimetext->content = MIMECONTENT_MEM;
5532 mimetext->tmp = TRUE; /* must free content later */
5533 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5534 * and free the data, which we need later. */
5535 mimetext->data.mem = g_strdup(buf);
5536 mimetext->type = MIMETYPE_TEXT;
5537 mimetext->subtype = g_strdup("plain");
5538 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5539 g_strdup(out_codeset));
5541 /* protect trailing spaces when signing message */
5542 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5543 privacy_system_can_sign(compose->privacy_system)) {
5544 encoding = ENC_QUOTED_PRINTABLE;
5547 debug_print("main text: %zd bytes encoded as %s in %d\n",
5548 strlen(buf), out_codeset, encoding);
5550 /* check for line length limit */
5551 if (action == COMPOSE_WRITE_FOR_SEND &&
5552 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5553 check_line_length(buf, 1000, &line) < 0) {
5557 msg = g_strdup_printf
5558 (_("Line %d exceeds the line length limit (998 bytes).\n"
5559 "The contents of the message might be broken on the way to the delivery.\n"
5561 "Send it anyway?"), line + 1);
5562 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5564 if (aval != G_ALERTALTERNATE) {
5570 if (encoding != ENC_UNKNOWN)
5571 procmime_encode_content(mimetext, encoding);
5573 /* append attachment parts */
5574 if (compose_use_attach(compose) && attach_parts) {
5575 MimeInfo *mimempart;
5576 gchar *boundary = NULL;
5577 mimempart = procmime_mimeinfo_new();
5578 mimempart->content = MIMECONTENT_EMPTY;
5579 mimempart->type = MIMETYPE_MULTIPART;
5580 mimempart->subtype = g_strdup("mixed");
5584 boundary = generate_mime_boundary(NULL);
5585 } while (strstr(buf, boundary) != NULL);
5587 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5590 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5592 g_node_append(mimempart->node, mimetext->node);
5593 g_node_append(mimemsg->node, mimempart->node);
5595 if (compose_add_attachments(compose, mimempart) < 0)
5598 g_node_append(mimemsg->node, mimetext->node);
5602 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5603 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5604 /* extract name and address */
5605 if (strstr(spec, " <") && strstr(spec, ">")) {
5606 from_addr = g_strdup(strrchr(spec, '<')+1);
5607 *(strrchr(from_addr, '>')) = '\0';
5608 from_name = g_strdup(spec);
5609 *(strrchr(from_name, '<')) = '\0';
5616 /* sign message if sending */
5617 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5618 privacy_system_can_sign(compose->privacy_system))
5619 if (!privacy_sign(compose->privacy_system, mimemsg,
5620 compose->account, from_addr)) {
5627 procmime_write_mimeinfo(mimemsg, fp);
5629 procmime_mimeinfo_free_all(mimemsg);
5634 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5636 GtkTextBuffer *buffer;
5637 GtkTextIter start, end;
5642 if ((fp = g_fopen(file, "wb")) == NULL) {
5643 FILE_OP_ERROR(file, "fopen");
5647 /* chmod for security */
5648 if (change_file_mode_rw(fp, file) < 0) {
5649 FILE_OP_ERROR(file, "chmod");
5650 g_warning("can't change file mode\n");
5653 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5654 gtk_text_buffer_get_start_iter(buffer, &start);
5655 gtk_text_buffer_get_end_iter(buffer, &end);
5656 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5658 chars = conv_codeset_strdup
5659 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5662 if (!chars) return -1;
5665 len = strlen(chars);
5666 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5667 FILE_OP_ERROR(file, "fwrite");
5676 if (fclose(fp) == EOF) {
5677 FILE_OP_ERROR(file, "fclose");
5684 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5687 MsgInfo *msginfo = compose->targetinfo;
5689 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5690 if (!msginfo) return -1;
5692 if (!force && MSG_IS_LOCKED(msginfo->flags))
5695 item = msginfo->folder;
5696 cm_return_val_if_fail(item != NULL, -1);
5698 if (procmsg_msg_exist(msginfo) &&
5699 (folder_has_parent_of_type(item, F_QUEUE) ||
5700 folder_has_parent_of_type(item, F_DRAFT)
5701 || msginfo == compose->autosaved_draft)) {
5702 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5703 g_warning("can't remove the old message\n");
5706 debug_print("removed reedit target %d\n", msginfo->msgnum);
5713 static void compose_remove_draft(Compose *compose)
5716 MsgInfo *msginfo = compose->targetinfo;
5717 drafts = account_get_special_folder(compose->account, F_DRAFT);
5719 if (procmsg_msg_exist(msginfo)) {
5720 folder_item_remove_msg(drafts, msginfo->msgnum);
5725 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5726 gboolean remove_reedit_target)
5728 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5731 static gboolean compose_warn_encryption(Compose *compose)
5733 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5734 AlertValue val = G_ALERTALTERNATE;
5736 if (warning == NULL)
5739 val = alertpanel_full(_("Encryption warning"), warning,
5740 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5741 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5742 if (val & G_ALERTDISABLE) {
5743 val &= ~G_ALERTDISABLE;
5744 if (val == G_ALERTALTERNATE)
5745 privacy_inhibit_encrypt_warning(compose->privacy_system,
5749 if (val == G_ALERTALTERNATE) {
5756 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5757 gchar **msgpath, gboolean check_subject,
5758 gboolean remove_reedit_target)
5765 PrefsAccount *mailac = NULL, *newsac = NULL;
5766 gboolean err = FALSE;
5768 debug_print("queueing message...\n");
5769 cm_return_val_if_fail(compose->account != NULL, -1);
5771 if (compose_check_entries(compose, check_subject) == FALSE) {
5772 if (compose->batch) {
5773 gtk_widget_show_all(compose->window);
5778 if (!compose->to_list && !compose->newsgroup_list) {
5779 g_warning("can't get recipient list.");
5783 if (compose->to_list) {
5784 if (compose->account->protocol != A_NNTP)
5785 mailac = compose->account;
5786 else if (cur_account && cur_account->protocol != A_NNTP)
5787 mailac = cur_account;
5788 else if (!(mailac = compose_current_mail_account())) {
5789 alertpanel_error(_("No account for sending mails available!"));
5794 if (compose->newsgroup_list) {
5795 if (compose->account->protocol == A_NNTP)
5796 newsac = compose->account;
5798 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5803 /* write queue header */
5804 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5805 G_DIR_SEPARATOR, compose, (guint) rand());
5806 debug_print("queuing to %s\n", tmp);
5807 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5808 FILE_OP_ERROR(tmp, "fopen");
5813 if (change_file_mode_rw(fp, tmp) < 0) {
5814 FILE_OP_ERROR(tmp, "chmod");
5815 g_warning("can't change file mode\n");
5818 /* queueing variables */
5819 err |= (fprintf(fp, "AF:\n") < 0);
5820 err |= (fprintf(fp, "NF:0\n") < 0);
5821 err |= (fprintf(fp, "PS:10\n") < 0);
5822 err |= (fprintf(fp, "SRH:1\n") < 0);
5823 err |= (fprintf(fp, "SFN:\n") < 0);
5824 err |= (fprintf(fp, "DSR:\n") < 0);
5826 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5828 err |= (fprintf(fp, "MID:\n") < 0);
5829 err |= (fprintf(fp, "CFG:\n") < 0);
5830 err |= (fprintf(fp, "PT:0\n") < 0);
5831 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5832 err |= (fprintf(fp, "RQ:\n") < 0);
5834 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5836 err |= (fprintf(fp, "SSV:\n") < 0);
5838 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5840 err |= (fprintf(fp, "NSV:\n") < 0);
5841 err |= (fprintf(fp, "SSH:\n") < 0);
5842 /* write recepient list */
5843 if (compose->to_list) {
5844 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5845 for (cur = compose->to_list->next; cur != NULL;
5847 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5848 err |= (fprintf(fp, "\n") < 0);
5850 /* write newsgroup list */
5851 if (compose->newsgroup_list) {
5852 err |= (fprintf(fp, "NG:") < 0);
5853 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5854 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5855 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5856 err |= (fprintf(fp, "\n") < 0);
5858 /* Sylpheed account IDs */
5860 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5862 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5865 if (compose->privacy_system != NULL) {
5866 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5867 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5868 if (compose->use_encryption) {
5870 if (!compose_warn_encryption(compose)) {
5876 if (mailac && mailac->encrypt_to_self) {
5877 GSList *tmp_list = g_slist_copy(compose->to_list);
5878 tmp_list = g_slist_append(tmp_list, compose->account->address);
5879 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5880 g_slist_free(tmp_list);
5882 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5884 if (encdata != NULL) {
5885 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5886 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5887 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5889 } /* else we finally dont want to encrypt */
5891 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5892 /* and if encdata was null, it means there's been a problem in
5895 g_warning("failed to write queue message");
5905 /* Save copy folder */
5906 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5907 gchar *savefolderid;
5909 savefolderid = compose_get_save_to(compose);
5910 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5911 g_free(savefolderid);
5913 /* Save copy folder */
5914 if (compose->return_receipt) {
5915 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5917 /* Message-ID of message replying to */
5918 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5921 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5922 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5925 /* Message-ID of message forwarding to */
5926 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5929 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5930 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5934 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5935 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5937 /* end of headers */
5938 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5940 if (compose->redirect_filename != NULL) {
5941 if (compose_redirect_write_to_file(compose, fp) < 0) {
5949 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5953 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5957 g_warning("failed to write queue message\n");
5963 if (fclose(fp) == EOF) {
5964 FILE_OP_ERROR(tmp, "fclose");
5970 if (item && *item) {
5973 queue = account_get_special_folder(compose->account, F_QUEUE);
5976 g_warning("can't find queue folder\n");
5981 folder_item_scan(queue);
5982 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5983 g_warning("can't queue the message\n");
5989 if (msgpath == NULL) {
5995 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5996 compose_remove_reedit_target(compose, FALSE);
5999 if ((msgnum != NULL) && (item != NULL)) {
6007 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6010 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6012 struct stat statbuf;
6013 gchar *type, *subtype;
6014 GtkTreeModel *model;
6017 model = gtk_tree_view_get_model(tree_view);
6019 if (!gtk_tree_model_get_iter_first(model, &iter))
6022 gtk_tree_model_get(model, &iter,
6026 if (!is_file_exist(ainfo->file)) {
6027 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6028 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6029 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6031 if (val == G_ALERTDEFAULT) {
6036 mimepart = procmime_mimeinfo_new();
6037 mimepart->content = MIMECONTENT_FILE;
6038 mimepart->data.filename = g_strdup(ainfo->file);
6039 mimepart->tmp = FALSE; /* or we destroy our attachment */
6040 mimepart->offset = 0;
6042 g_stat(ainfo->file, &statbuf);
6043 mimepart->length = statbuf.st_size;
6045 type = g_strdup(ainfo->content_type);
6047 if (!strchr(type, '/')) {
6049 type = g_strdup("application/octet-stream");
6052 subtype = strchr(type, '/') + 1;
6053 *(subtype - 1) = '\0';
6054 mimepart->type = procmime_get_media_type(type);
6055 mimepart->subtype = g_strdup(subtype);
6058 if (mimepart->type == MIMETYPE_MESSAGE &&
6059 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6060 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6061 } else if (mimepart->type == MIMETYPE_TEXT) {
6062 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6063 /* Text parts with no name come from multipart/alternative
6064 * forwards. Make sure the recipient won't look at the
6065 * original HTML part by mistake. */
6066 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6067 ainfo->name = g_strdup_printf(_("Original %s part"),
6071 g_hash_table_insert(mimepart->typeparameters,
6072 g_strdup("charset"), g_strdup(ainfo->charset));
6074 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6075 if (mimepart->type == MIMETYPE_APPLICATION &&
6076 !strcmp2(mimepart->subtype, "octet-stream"))
6077 g_hash_table_insert(mimepart->typeparameters,
6078 g_strdup("name"), g_strdup(ainfo->name));
6079 g_hash_table_insert(mimepart->dispositionparameters,
6080 g_strdup("filename"), g_strdup(ainfo->name));
6081 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6084 if (mimepart->type == MIMETYPE_MESSAGE
6085 || mimepart->type == MIMETYPE_MULTIPART)
6086 ainfo->encoding = ENC_BINARY;
6087 else if (compose->use_signing) {
6088 if (ainfo->encoding == ENC_7BIT)
6089 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6090 else if (ainfo->encoding == ENC_8BIT)
6091 ainfo->encoding = ENC_BASE64;
6096 procmime_encode_content(mimepart, ainfo->encoding);
6098 g_node_append(parent->node, mimepart->node);
6099 } while (gtk_tree_model_iter_next(model, &iter));
6104 #define IS_IN_CUSTOM_HEADER(header) \
6105 (compose->account->add_customhdr && \
6106 custom_header_find(compose->account->customhdr_list, header) != NULL)
6108 static void compose_add_headerfield_from_headerlist(Compose *compose,
6110 const gchar *fieldname,
6111 const gchar *seperator)
6113 gchar *str, *fieldname_w_colon;
6114 gboolean add_field = FALSE;
6116 ComposeHeaderEntry *headerentry;
6117 const gchar *headerentryname;
6118 const gchar *trans_fieldname;
6121 if (IS_IN_CUSTOM_HEADER(fieldname))
6124 debug_print("Adding %s-fields\n", fieldname);
6126 fieldstr = g_string_sized_new(64);
6128 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6129 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6131 for (list = compose->header_list; list; list = list->next) {
6132 headerentry = ((ComposeHeaderEntry *)list->data);
6133 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6135 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6136 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6138 if (str[0] != '\0') {
6140 g_string_append(fieldstr, seperator);
6141 g_string_append(fieldstr, str);
6150 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6151 compose_convert_header
6152 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6153 strlen(fieldname) + 2, TRUE);
6154 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6158 g_free(fieldname_w_colon);
6159 g_string_free(fieldstr, TRUE);
6164 static gchar *compose_get_manual_headers_info(Compose *compose)
6166 GString *sh_header = g_string_new(" ");
6168 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6170 for (list = compose->header_list; list; list = list->next) {
6171 ComposeHeaderEntry *headerentry;
6174 gchar *headername_wcolon;
6175 const gchar *headername_trans;
6177 gboolean standard_header = FALSE;
6179 headerentry = ((ComposeHeaderEntry *)list->data);
6181 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6183 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6188 if (!strstr(tmp, ":")) {
6189 headername_wcolon = g_strconcat(tmp, ":", NULL);
6190 headername = g_strdup(tmp);
6192 headername_wcolon = g_strdup(tmp);
6193 headername = g_strdup(strtok(tmp, ":"));
6197 string = std_headers;
6198 while (*string != NULL) {
6199 headername_trans = prefs_common_translated_header_name(*string);
6200 if (!strcmp(headername_trans, headername_wcolon))
6201 standard_header = TRUE;
6204 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6205 g_string_append_printf(sh_header, "%s ", headername);
6207 g_free(headername_wcolon);
6209 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6210 return g_string_free(sh_header, FALSE);
6213 static gchar *compose_get_header(Compose *compose)
6215 gchar buf[BUFFSIZE];
6216 const gchar *entry_str;
6220 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6222 gchar *from_name = NULL, *from_address = NULL;
6225 cm_return_val_if_fail(compose->account != NULL, NULL);
6226 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6228 header = g_string_sized_new(64);
6231 get_rfc822_date(buf, sizeof(buf));
6232 g_string_append_printf(header, "Date: %s\n", buf);
6236 if (compose->account->name && *compose->account->name) {
6238 QUOTE_IF_REQUIRED(buf, compose->account->name);
6239 tmp = g_strdup_printf("%s <%s>",
6240 buf, compose->account->address);
6242 tmp = g_strdup_printf("%s",
6243 compose->account->address);
6245 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6246 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6248 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6249 from_address = g_strdup(compose->account->address);
6251 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6252 /* extract name and address */
6253 if (strstr(spec, " <") && strstr(spec, ">")) {
6254 from_address = g_strdup(strrchr(spec, '<')+1);
6255 *(strrchr(from_address, '>')) = '\0';
6256 from_name = g_strdup(spec);
6257 *(strrchr(from_name, '<')) = '\0';
6260 from_address = g_strdup(spec);
6267 if (from_name && *from_name) {
6268 compose_convert_header
6269 (compose, buf, sizeof(buf), from_name,
6270 strlen("From: "), TRUE);
6271 QUOTE_IF_REQUIRED(name, buf);
6273 g_string_append_printf(header, "From: %s <%s>\n",
6274 name, from_address);
6276 g_string_append_printf(header, "From: %s\n", from_address);
6279 g_free(from_address);
6282 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6285 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6288 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6292 * If this account is a NNTP account remove Bcc header from
6293 * message body since it otherwise will be publicly shown
6295 if (compose->account->protocol != A_NNTP)
6296 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6299 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6301 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6304 compose_convert_header(compose, buf, sizeof(buf), str,
6305 strlen("Subject: "), FALSE);
6306 g_string_append_printf(header, "Subject: %s\n", buf);
6312 if (compose->account->set_domain && compose->account->domain) {
6313 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6314 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6315 g_snprintf(buf, sizeof(buf), "%s",
6316 strchr(compose->account->address, '@') ?
6317 strchr(compose->account->address, '@')+1 :
6318 compose->account->address);
6320 g_snprintf(buf, sizeof(buf), "%s", "");
6323 if (compose->account->gen_msgid) {
6325 if (compose->account->msgid_with_addr) {
6326 addr = compose->account->address;
6328 generate_msgid(buf, sizeof(buf), addr);
6329 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6330 compose->msgid = g_strdup(buf);
6332 compose->msgid = NULL;
6335 if (compose->remove_references == FALSE) {
6337 if (compose->inreplyto && compose->to_list)
6338 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6341 if (compose->references)
6342 g_string_append_printf(header, "References: %s\n", compose->references);
6346 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6349 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6352 if (compose->account->organization &&
6353 strlen(compose->account->organization) &&
6354 !IS_IN_CUSTOM_HEADER("Organization")) {
6355 compose_convert_header(compose, buf, sizeof(buf),
6356 compose->account->organization,
6357 strlen("Organization: "), FALSE);
6358 g_string_append_printf(header, "Organization: %s\n", buf);
6361 /* Program version and system info */
6362 if (compose->account->gen_xmailer &&
6363 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6364 !compose->newsgroup_list) {
6365 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6367 gtk_major_version, gtk_minor_version, gtk_micro_version,
6370 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6371 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6373 gtk_major_version, gtk_minor_version, gtk_micro_version,
6377 /* custom headers */
6378 if (compose->account->add_customhdr) {
6381 for (cur = compose->account->customhdr_list; cur != NULL;
6383 CustomHeader *chdr = (CustomHeader *)cur->data;
6385 if (custom_header_is_allowed(chdr->name)
6386 && chdr->value != NULL
6387 && *(chdr->value) != '\0') {
6388 compose_convert_header
6389 (compose, buf, sizeof(buf),
6391 strlen(chdr->name) + 2, FALSE);
6392 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6397 /* Automatic Faces and X-Faces */
6398 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6399 g_string_append_printf(header, "X-Face: %s\n", buf);
6401 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6402 g_string_append_printf(header, "X-Face: %s\n", buf);
6404 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6405 g_string_append_printf(header, "Face: %s\n", buf);
6407 else if (get_default_face (buf, sizeof(buf)) == 0) {
6408 g_string_append_printf(header, "Face: %s\n", buf);
6412 switch (compose->priority) {
6413 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6414 "X-Priority: 1 (Highest)\n");
6416 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6417 "X-Priority: 2 (High)\n");
6419 case PRIORITY_NORMAL: break;
6420 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6421 "X-Priority: 4 (Low)\n");
6423 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6424 "X-Priority: 5 (Lowest)\n");
6426 default: debug_print("compose: priority unknown : %d\n",
6430 /* Request Return Receipt */
6431 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6432 if (compose->return_receipt) {
6433 if (compose->account->name
6434 && *compose->account->name) {
6435 compose_convert_header(compose, buf, sizeof(buf),
6436 compose->account->name,
6437 strlen("Disposition-Notification-To: "),
6439 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6441 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6445 /* get special headers */
6446 for (list = compose->header_list; list; list = list->next) {
6447 ComposeHeaderEntry *headerentry;
6450 gchar *headername_wcolon;
6451 const gchar *headername_trans;
6454 gboolean standard_header = FALSE;
6456 headerentry = ((ComposeHeaderEntry *)list->data);
6458 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6460 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6465 if (!strstr(tmp, ":")) {
6466 headername_wcolon = g_strconcat(tmp, ":", NULL);
6467 headername = g_strdup(tmp);
6469 headername_wcolon = g_strdup(tmp);
6470 headername = g_strdup(strtok(tmp, ":"));
6474 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6475 Xstrdup_a(headervalue, entry_str, return NULL);
6476 subst_char(headervalue, '\r', ' ');
6477 subst_char(headervalue, '\n', ' ');
6478 string = std_headers;
6479 while (*string != NULL) {
6480 headername_trans = prefs_common_translated_header_name(*string);
6481 if (!strcmp(headername_trans, headername_wcolon))
6482 standard_header = TRUE;
6485 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6486 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6489 g_free(headername_wcolon);
6493 g_string_free(header, FALSE);
6498 #undef IS_IN_CUSTOM_HEADER
6500 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6501 gint header_len, gboolean addr_field)
6503 gchar *tmpstr = NULL;
6504 const gchar *out_codeset = NULL;
6506 cm_return_if_fail(src != NULL);
6507 cm_return_if_fail(dest != NULL);
6509 if (len < 1) return;
6511 tmpstr = g_strdup(src);
6513 subst_char(tmpstr, '\n', ' ');
6514 subst_char(tmpstr, '\r', ' ');
6517 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6518 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6519 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6524 codeconv_set_strict(TRUE);
6525 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6526 conv_get_charset_str(compose->out_encoding));
6527 codeconv_set_strict(FALSE);
6529 if (!dest || *dest == '\0') {
6530 gchar *test_conv_global_out = NULL;
6531 gchar *test_conv_reply = NULL;
6533 /* automatic mode. be automatic. */
6534 codeconv_set_strict(TRUE);
6536 out_codeset = conv_get_outgoing_charset_str();
6538 debug_print("trying to convert to %s\n", out_codeset);
6539 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6542 if (!test_conv_global_out && compose->orig_charset
6543 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6544 out_codeset = compose->orig_charset;
6545 debug_print("failure; trying to convert to %s\n", out_codeset);
6546 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6549 if (!test_conv_global_out && !test_conv_reply) {
6551 out_codeset = CS_INTERNAL;
6552 debug_print("finally using %s\n", out_codeset);
6554 g_free(test_conv_global_out);
6555 g_free(test_conv_reply);
6556 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6558 codeconv_set_strict(FALSE);
6563 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6567 cm_return_if_fail(user_data != NULL);
6569 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6570 g_strstrip(address);
6571 if (*address != '\0') {
6572 gchar *name = procheader_get_fromname(address);
6573 extract_address(address);
6574 #ifndef USE_NEW_ADDRBOOK
6575 addressbook_add_contact(name, address, NULL, NULL);
6577 debug_print("%s: %s\n", name, address);
6578 if (addressadd_selection(name, address, NULL, NULL)) {
6579 debug_print( "addressbook_add_contact - added\n" );
6586 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6588 GtkWidget *menuitem;
6591 cm_return_if_fail(menu != NULL);
6592 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6594 menuitem = gtk_separator_menu_item_new();
6595 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6596 gtk_widget_show(menuitem);
6598 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6599 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6601 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6602 g_strstrip(address);
6603 if (*address == '\0') {
6604 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6607 g_signal_connect(G_OBJECT(menuitem), "activate",
6608 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6609 gtk_widget_show(menuitem);
6612 void compose_add_extra_header(gchar *header, GtkListStore *model)
6615 if (strcmp(header, "")) {
6616 COMBOBOX_ADD(model, header, COMPOSE_TO);
6620 void compose_add_extra_header_entries(GtkListStore *model)
6624 gchar buf[BUFFSIZE];
6627 if (extra_headers == NULL) {
6628 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6629 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6630 debug_print("extra headers file not found\n");
6631 goto extra_headers_done;
6633 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6634 lastc = strlen(buf) - 1; /* remove trailing control chars */
6635 while (lastc >= 0 && buf[lastc] != ':')
6636 buf[lastc--] = '\0';
6637 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6638 buf[lastc] = '\0'; /* remove trailing : for comparison */
6639 if (custom_header_is_allowed(buf)) {
6641 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6644 g_message("disallowed extra header line: %s\n", buf);
6648 g_message("invalid extra header line: %s\n", buf);
6654 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6655 extra_headers = g_slist_reverse(extra_headers);
6657 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6660 static void compose_create_header_entry(Compose *compose)
6662 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6669 const gchar *header = NULL;
6670 ComposeHeaderEntry *headerentry;
6671 gboolean standard_header = FALSE;
6672 GtkListStore *model;
6674 #if !(GTK_CHECK_VERSION(2,12,0))
6675 GtkTooltips *tips = compose->tooltips;
6678 headerentry = g_new0(ComposeHeaderEntry, 1);
6680 /* Combo box model */
6681 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6682 #if !GTK_CHECK_VERSION(2, 24, 0)
6683 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6685 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6687 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6689 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6691 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6692 COMPOSE_NEWSGROUPS);
6693 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6695 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6696 COMPOSE_FOLLOWUPTO);
6697 compose_add_extra_header_entries(model);
6700 #if GTK_CHECK_VERSION(2, 24, 0)
6701 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6702 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6703 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6704 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6705 gtk_combo_box_set_entry_text_column(combo, 0);
6707 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6708 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6709 G_CALLBACK(compose_grab_focus_cb), compose);
6710 gtk_widget_show(combo);
6713 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6714 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6717 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6718 compose->header_nextrow, compose->header_nextrow+1,
6719 GTK_SHRINK, GTK_FILL, 0, 0);
6720 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6721 const gchar *last_header_entry = gtk_entry_get_text(
6722 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6724 while (*string != NULL) {
6725 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6726 standard_header = TRUE;
6729 if (standard_header)
6730 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6732 if (!compose->header_last || !standard_header) {
6733 switch(compose->account->protocol) {
6735 header = prefs_common_translated_header_name("Newsgroups:");
6738 header = prefs_common_translated_header_name("To:");
6743 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6745 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6746 G_CALLBACK(compose_grab_focus_cb), compose);
6748 /* Entry field with cleanup button */
6749 button = gtk_button_new();
6750 gtk_button_set_image(GTK_BUTTON(button),
6751 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6752 gtk_widget_show(button);
6753 CLAWS_SET_TIP(button,
6754 _("Delete entry contents"));
6755 entry = gtk_entry_new();
6756 gtk_widget_show(entry);
6757 CLAWS_SET_TIP(entry,
6758 _("Use <tab> to autocomplete from addressbook"));
6759 hbox = gtk_hbox_new (FALSE, 0);
6760 gtk_widget_show(hbox);
6761 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6762 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6763 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6764 compose->header_nextrow, compose->header_nextrow+1,
6765 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6767 g_signal_connect(G_OBJECT(entry), "key-press-event",
6768 G_CALLBACK(compose_headerentry_key_press_event_cb),
6770 g_signal_connect(G_OBJECT(entry), "changed",
6771 G_CALLBACK(compose_headerentry_changed_cb),
6773 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6774 G_CALLBACK(compose_grab_focus_cb), compose);
6776 g_signal_connect(G_OBJECT(button), "clicked",
6777 G_CALLBACK(compose_headerentry_button_clicked_cb),
6781 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6782 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6783 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6784 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6785 G_CALLBACK(compose_header_drag_received_cb),
6787 g_signal_connect(G_OBJECT(entry), "drag-drop",
6788 G_CALLBACK(compose_drag_drop),
6790 g_signal_connect(G_OBJECT(entry), "populate-popup",
6791 G_CALLBACK(compose_entry_popup_extend),
6794 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6796 headerentry->compose = compose;
6797 headerentry->combo = combo;
6798 headerentry->entry = entry;
6799 headerentry->button = button;
6800 headerentry->hbox = hbox;
6801 headerentry->headernum = compose->header_nextrow;
6802 headerentry->type = PREF_NONE;
6804 compose->header_nextrow++;
6805 compose->header_last = headerentry;
6806 compose->header_list =
6807 g_slist_append(compose->header_list,
6811 static void compose_add_header_entry(Compose *compose, const gchar *header,
6812 gchar *text, ComposePrefType pref_type)
6814 ComposeHeaderEntry *last_header = compose->header_last;
6815 gchar *tmp = g_strdup(text), *email;
6816 gboolean replyto_hdr;
6818 replyto_hdr = (!strcasecmp(header,
6819 prefs_common_translated_header_name("Reply-To:")) ||
6821 prefs_common_translated_header_name("Followup-To:")) ||
6823 prefs_common_translated_header_name("In-Reply-To:")));
6825 extract_address(tmp);
6826 email = g_utf8_strdown(tmp, -1);
6828 if (replyto_hdr == FALSE &&
6829 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6831 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6832 header, text, (gint) pref_type);
6838 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6839 gtk_entry_set_text(GTK_ENTRY(
6840 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6842 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6843 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6844 last_header->type = pref_type;
6846 if (replyto_hdr == FALSE)
6847 g_hash_table_insert(compose->email_hashtable, email,
6848 GUINT_TO_POINTER(1));
6855 static void compose_destroy_headerentry(Compose *compose,
6856 ComposeHeaderEntry *headerentry)
6858 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6861 extract_address(text);
6862 email = g_utf8_strdown(text, -1);
6863 g_hash_table_remove(compose->email_hashtable, email);
6867 gtk_widget_destroy(headerentry->combo);
6868 gtk_widget_destroy(headerentry->entry);
6869 gtk_widget_destroy(headerentry->button);
6870 gtk_widget_destroy(headerentry->hbox);
6871 g_free(headerentry);
6874 static void compose_remove_header_entries(Compose *compose)
6877 for (list = compose->header_list; list; list = list->next)
6878 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6880 compose->header_last = NULL;
6881 g_slist_free(compose->header_list);
6882 compose->header_list = NULL;
6883 compose->header_nextrow = 1;
6884 compose_create_header_entry(compose);
6887 static GtkWidget *compose_create_header(Compose *compose)
6889 GtkWidget *from_optmenu_hbox;
6890 GtkWidget *header_scrolledwin_main;
6891 GtkWidget *header_table_main;
6892 GtkWidget *header_scrolledwin;
6893 GtkWidget *header_table;
6895 /* parent with account selection and from header */
6896 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6897 gtk_widget_show(header_scrolledwin_main);
6898 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6900 header_table_main = gtk_table_new(2, 2, FALSE);
6901 gtk_widget_show(header_table_main);
6902 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6903 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6904 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6906 from_optmenu_hbox = compose_account_option_menu_create(compose);
6907 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6908 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6910 /* child with header labels and entries */
6911 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6912 gtk_widget_show(header_scrolledwin);
6913 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6915 header_table = gtk_table_new(2, 2, FALSE);
6916 gtk_widget_show(header_table);
6917 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6918 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6919 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6921 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6922 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6924 compose->header_table = header_table;
6925 compose->header_list = NULL;
6926 compose->header_nextrow = 0;
6928 compose_create_header_entry(compose);
6930 compose->table = NULL;
6932 return header_scrolledwin_main;
6935 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6937 Compose *compose = (Compose *)data;
6938 GdkEventButton event;
6941 event.time = gtk_get_current_event_time();
6943 return attach_button_pressed(compose->attach_clist, &event, compose);
6946 static GtkWidget *compose_create_attach(Compose *compose)
6948 GtkWidget *attach_scrwin;
6949 GtkWidget *attach_clist;
6951 GtkListStore *store;
6952 GtkCellRenderer *renderer;
6953 GtkTreeViewColumn *column;
6954 GtkTreeSelection *selection;
6956 /* attachment list */
6957 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6958 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6959 GTK_POLICY_AUTOMATIC,
6960 GTK_POLICY_AUTOMATIC);
6961 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6963 store = gtk_list_store_new(N_ATTACH_COLS,
6969 G_TYPE_AUTO_POINTER,
6971 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6972 (GTK_TREE_MODEL(store)));
6973 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6974 g_object_unref(store);
6976 renderer = gtk_cell_renderer_text_new();
6977 column = gtk_tree_view_column_new_with_attributes
6978 (_("Mime type"), renderer, "text",
6979 COL_MIMETYPE, NULL);
6980 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6982 renderer = gtk_cell_renderer_text_new();
6983 column = gtk_tree_view_column_new_with_attributes
6984 (_("Size"), renderer, "text",
6986 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6988 renderer = gtk_cell_renderer_text_new();
6989 column = gtk_tree_view_column_new_with_attributes
6990 (_("Name"), renderer, "text",
6992 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6994 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6995 prefs_common.use_stripes_everywhere);
6996 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6997 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6999 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
7000 G_CALLBACK(attach_selected), compose);
7001 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
7002 G_CALLBACK(attach_button_pressed), compose);
7004 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
7005 G_CALLBACK(popup_attach_button_pressed), compose);
7007 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
7008 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7009 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
7010 G_CALLBACK(popup_attach_button_pressed), compose);
7012 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7013 G_CALLBACK(attach_key_pressed), compose);
7016 gtk_drag_dest_set(attach_clist,
7017 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7018 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7019 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7020 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7021 G_CALLBACK(compose_attach_drag_received_cb),
7023 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7024 G_CALLBACK(compose_drag_drop),
7027 compose->attach_scrwin = attach_scrwin;
7028 compose->attach_clist = attach_clist;
7030 return attach_scrwin;
7033 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7034 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7036 static GtkWidget *compose_create_others(Compose *compose)
7039 GtkWidget *savemsg_checkbtn;
7040 GtkWidget *savemsg_combo;
7041 GtkWidget *savemsg_select;
7044 gchar *folderidentifier;
7046 /* Table for settings */
7047 table = gtk_table_new(3, 1, FALSE);
7048 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7049 gtk_widget_show(table);
7050 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7053 /* Save Message to folder */
7054 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7055 gtk_widget_show(savemsg_checkbtn);
7056 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7057 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7058 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7060 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7061 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7063 #if !GTK_CHECK_VERSION(2, 24, 0)
7064 savemsg_combo = gtk_combo_box_entry_new_text();
7066 savemsg_combo = gtk_combo_box_text_new_with_entry();
7068 compose->savemsg_checkbtn = savemsg_checkbtn;
7069 compose->savemsg_combo = savemsg_combo;
7070 gtk_widget_show(savemsg_combo);
7072 if (prefs_common.compose_save_to_history)
7073 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7074 prefs_common.compose_save_to_history);
7076 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7077 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7078 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7079 G_CALLBACK(compose_grab_focus_cb), compose);
7080 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7081 folderidentifier = folder_item_get_identifier(account_get_special_folder
7082 (compose->account, F_OUTBOX));
7083 compose_set_save_to(compose, folderidentifier);
7084 g_free(folderidentifier);
7087 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7088 gtk_widget_show(savemsg_select);
7089 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7090 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7091 G_CALLBACK(compose_savemsg_select_cb),
7097 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7099 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7100 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7103 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7108 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7111 path = folder_item_get_identifier(dest);
7113 compose_set_save_to(compose, path);
7117 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7118 GdkAtom clip, GtkTextIter *insert_place);
7121 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7125 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7127 if (event->button == 3) {
7129 GtkTextIter sel_start, sel_end;
7130 gboolean stuff_selected;
7132 /* move the cursor to allow GtkAspell to check the word
7133 * under the mouse */
7134 if (event->x && event->y) {
7135 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7136 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7138 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7141 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7142 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7145 stuff_selected = gtk_text_buffer_get_selection_bounds(
7147 &sel_start, &sel_end);
7149 gtk_text_buffer_place_cursor (buffer, &iter);
7150 /* reselect stuff */
7152 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7153 gtk_text_buffer_select_range(buffer,
7154 &sel_start, &sel_end);
7156 return FALSE; /* pass the event so that the right-click goes through */
7159 if (event->button == 2) {
7164 /* get the middle-click position to paste at the correct place */
7165 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7166 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7168 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7171 entry_paste_clipboard(compose, text,
7172 prefs_common.linewrap_pastes,
7173 GDK_SELECTION_PRIMARY, &iter);
7181 static void compose_spell_menu_changed(void *data)
7183 Compose *compose = (Compose *)data;
7185 GtkWidget *menuitem;
7186 GtkWidget *parent_item;
7187 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7190 if (compose->gtkaspell == NULL)
7193 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7194 "/Menu/Spelling/Options");
7196 /* setting the submenu removes /Spelling/Options from the factory
7197 * so we need to save it */
7199 if (parent_item == NULL) {
7200 parent_item = compose->aspell_options_menu;
7201 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7203 compose->aspell_options_menu = parent_item;
7205 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7207 spell_menu = g_slist_reverse(spell_menu);
7208 for (items = spell_menu;
7209 items; items = items->next) {
7210 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7211 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7212 gtk_widget_show(GTK_WIDGET(menuitem));
7214 g_slist_free(spell_menu);
7216 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7217 gtk_widget_show(parent_item);
7220 static void compose_dict_changed(void *data)
7222 Compose *compose = (Compose *) data;
7224 if(compose->gtkaspell &&
7225 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7228 gtkaspell_highlight_all(compose->gtkaspell);
7229 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7233 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7235 Compose *compose = (Compose *)data;
7236 GdkEventButton event;
7239 event.time = gtk_get_current_event_time();
7243 return text_clicked(compose->text, &event, compose);
7246 static gboolean compose_force_window_origin = TRUE;
7247 static Compose *compose_create(PrefsAccount *account,
7256 GtkWidget *handlebox;
7258 GtkWidget *notebook;
7260 GtkWidget *attach_hbox;
7261 GtkWidget *attach_lab1;
7262 GtkWidget *attach_lab2;
7267 GtkWidget *subject_hbox;
7268 GtkWidget *subject_frame;
7269 GtkWidget *subject_entry;
7273 GtkWidget *edit_vbox;
7274 GtkWidget *ruler_hbox;
7276 GtkWidget *scrolledwin;
7278 GtkTextBuffer *buffer;
7279 GtkClipboard *clipboard;
7281 UndoMain *undostruct;
7283 GtkWidget *popupmenu;
7284 GtkWidget *tmpl_menu;
7285 GtkActionGroup *action_group = NULL;
7288 GtkAspell * gtkaspell = NULL;
7291 static GdkGeometry geometry;
7293 cm_return_val_if_fail(account != NULL, NULL);
7295 debug_print("Creating compose window...\n");
7296 compose = g_new0(Compose, 1);
7298 compose->batch = batch;
7299 compose->account = account;
7300 compose->folder = folder;
7302 compose->mutex = cm_mutex_new();
7303 compose->set_cursor_pos = -1;
7305 #if !(GTK_CHECK_VERSION(2,12,0))
7306 compose->tooltips = tips;
7309 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7311 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7312 gtk_widget_set_size_request(window, prefs_common.compose_width,
7313 prefs_common.compose_height);
7315 if (!geometry.max_width) {
7316 geometry.max_width = gdk_screen_width();
7317 geometry.max_height = gdk_screen_height();
7320 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7321 &geometry, GDK_HINT_MAX_SIZE);
7322 if (!geometry.min_width) {
7323 geometry.min_width = 600;
7324 geometry.min_height = 440;
7326 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7327 &geometry, GDK_HINT_MIN_SIZE);
7329 #ifndef GENERIC_UMPC
7330 if (compose_force_window_origin)
7331 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7332 prefs_common.compose_y);
7334 g_signal_connect(G_OBJECT(window), "delete_event",
7335 G_CALLBACK(compose_delete_cb), compose);
7336 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7337 gtk_widget_realize(window);
7339 gtkut_widget_set_composer_icon(window);
7341 vbox = gtk_vbox_new(FALSE, 0);
7342 gtk_container_add(GTK_CONTAINER(window), vbox);
7344 compose->ui_manager = gtk_ui_manager_new();
7345 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7346 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7347 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7348 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7349 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7350 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7351 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7352 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7353 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7354 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7357 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7359 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7362 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7363 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7365 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7367 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7368 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7369 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7372 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7373 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7374 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7375 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7376 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7377 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7378 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7379 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7380 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7382 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7383 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7386 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7387 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7391 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7392 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7394 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7396 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7405 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7406 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7407 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7409 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7410 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7411 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7414 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7419 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7420 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7421 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7422 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7423 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7431 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7432 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7433 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7446 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7453 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7469 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7470 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7472 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7476 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)
7477 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)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7483 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)
7484 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)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7488 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7489 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)
7490 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7492 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7493 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)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7496 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7498 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7499 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)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7502 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7505 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)
7506 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)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7512 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7513 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7517 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7518 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7519 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)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7527 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7529 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7530 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7531 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7532 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7535 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7537 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7538 gtk_widget_show_all(menubar);
7540 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7542 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7544 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7547 if (prefs_common.toolbar_detachable) {
7548 handlebox = gtk_handle_box_new();
7550 handlebox = gtk_hbox_new(FALSE, 0);
7552 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7554 gtk_widget_realize(handlebox);
7556 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7559 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7563 vbox2 = gtk_vbox_new(FALSE, 2);
7564 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7565 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7568 notebook = gtk_notebook_new();
7569 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7570 gtk_widget_show(notebook);
7572 /* header labels and entries */
7573 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7574 compose_create_header(compose),
7575 gtk_label_new_with_mnemonic(_("Hea_der")));
7576 /* attachment list */
7577 attach_hbox = gtk_hbox_new(FALSE, 0);
7578 gtk_widget_show(attach_hbox);
7580 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7581 gtk_widget_show(attach_lab1);
7582 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7584 attach_lab2 = gtk_label_new("");
7585 gtk_widget_show(attach_lab2);
7586 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7588 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7589 compose_create_attach(compose),
7592 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7593 compose_create_others(compose),
7594 gtk_label_new_with_mnemonic(_("Othe_rs")));
7597 subject_hbox = gtk_hbox_new(FALSE, 0);
7598 gtk_widget_show(subject_hbox);
7600 subject_frame = gtk_frame_new(NULL);
7601 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7602 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7603 gtk_widget_show(subject_frame);
7605 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7606 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7607 gtk_widget_show(subject);
7609 label = gtk_label_new(_("Subject:"));
7610 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7611 gtk_widget_show(label);
7614 subject_entry = claws_spell_entry_new();
7616 subject_entry = gtk_entry_new();
7618 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7619 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7620 G_CALLBACK(compose_grab_focus_cb), compose);
7621 gtk_widget_show(subject_entry);
7622 compose->subject_entry = subject_entry;
7623 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7625 edit_vbox = gtk_vbox_new(FALSE, 0);
7627 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7630 ruler_hbox = gtk_hbox_new(FALSE, 0);
7631 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7633 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7634 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7635 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7639 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7640 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7641 GTK_POLICY_AUTOMATIC,
7642 GTK_POLICY_AUTOMATIC);
7643 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7645 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7647 text = gtk_text_view_new();
7648 if (prefs_common.show_compose_margin) {
7649 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7650 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7652 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7653 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7654 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7655 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7656 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7658 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7659 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7660 G_CALLBACK(compose_notebook_size_alloc), compose);
7661 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7662 G_CALLBACK(compose_edit_size_alloc),
7664 g_signal_connect(G_OBJECT(buffer), "changed",
7665 G_CALLBACK(compose_changed_cb), compose);
7666 g_signal_connect(G_OBJECT(text), "grab_focus",
7667 G_CALLBACK(compose_grab_focus_cb), compose);
7668 g_signal_connect(G_OBJECT(buffer), "insert_text",
7669 G_CALLBACK(text_inserted), compose);
7670 g_signal_connect(G_OBJECT(text), "button_press_event",
7671 G_CALLBACK(text_clicked), compose);
7673 g_signal_connect(G_OBJECT(text), "popup-menu",
7674 G_CALLBACK(compose_popup_menu), compose);
7676 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7677 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7678 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7679 G_CALLBACK(compose_popup_menu), compose);
7681 g_signal_connect(G_OBJECT(subject_entry), "changed",
7682 G_CALLBACK(compose_changed_cb), compose);
7683 g_signal_connect(G_OBJECT(subject_entry), "activate",
7684 G_CALLBACK(compose_subject_entry_activated), compose);
7687 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7688 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7689 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7690 g_signal_connect(G_OBJECT(text), "drag_data_received",
7691 G_CALLBACK(compose_insert_drag_received_cb),
7693 g_signal_connect(G_OBJECT(text), "drag-drop",
7694 G_CALLBACK(compose_drag_drop),
7696 g_signal_connect(G_OBJECT(text), "key-press-event",
7697 G_CALLBACK(completion_set_focus_to_subject),
7699 gtk_widget_show_all(vbox);
7701 /* pane between attach clist and text */
7702 paned = gtk_vpaned_new();
7703 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7705 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7706 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7708 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7710 gtk_paned_add1(GTK_PANED(paned), notebook);
7711 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7712 gtk_widget_show_all(paned);
7715 if (prefs_common.textfont) {
7716 PangoFontDescription *font_desc;
7718 font_desc = pango_font_description_from_string
7719 (prefs_common.textfont);
7721 gtk_widget_modify_font(text, font_desc);
7722 pango_font_description_free(font_desc);
7726 gtk_action_group_add_actions(action_group, compose_popup_entries,
7727 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7728 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7729 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7730 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7731 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7732 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7733 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7735 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7737 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7738 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7739 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7741 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7743 undostruct = undo_init(text);
7744 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7747 address_completion_start(window);
7749 compose->window = window;
7750 compose->vbox = vbox;
7751 compose->menubar = menubar;
7752 compose->handlebox = handlebox;
7754 compose->vbox2 = vbox2;
7756 compose->paned = paned;
7758 compose->attach_label = attach_lab2;
7760 compose->notebook = notebook;
7761 compose->edit_vbox = edit_vbox;
7762 compose->ruler_hbox = ruler_hbox;
7763 compose->ruler = ruler;
7764 compose->scrolledwin = scrolledwin;
7765 compose->text = text;
7767 compose->focused_editable = NULL;
7769 compose->popupmenu = popupmenu;
7771 compose->tmpl_menu = tmpl_menu;
7773 compose->mode = mode;
7774 compose->rmode = mode;
7776 compose->targetinfo = NULL;
7777 compose->replyinfo = NULL;
7778 compose->fwdinfo = NULL;
7780 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7781 g_str_equal, (GDestroyNotify) g_free, NULL);
7783 compose->replyto = NULL;
7785 compose->bcc = NULL;
7786 compose->followup_to = NULL;
7788 compose->ml_post = NULL;
7790 compose->inreplyto = NULL;
7791 compose->references = NULL;
7792 compose->msgid = NULL;
7793 compose->boundary = NULL;
7795 compose->autowrap = prefs_common.autowrap;
7796 compose->autoindent = prefs_common.auto_indent;
7797 compose->use_signing = FALSE;
7798 compose->use_encryption = FALSE;
7799 compose->privacy_system = NULL;
7801 compose->modified = FALSE;
7803 compose->return_receipt = FALSE;
7805 compose->to_list = NULL;
7806 compose->newsgroup_list = NULL;
7808 compose->undostruct = undostruct;
7810 compose->sig_str = NULL;
7812 compose->exteditor_file = NULL;
7813 compose->exteditor_pid = -1;
7814 compose->exteditor_tag = -1;
7815 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7818 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7819 if (mode != COMPOSE_REDIRECT) {
7820 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7821 strcmp(prefs_common.dictionary, "")) {
7822 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7823 prefs_common.alt_dictionary,
7824 conv_get_locale_charset_str(),
7825 prefs_common.misspelled_col,
7826 prefs_common.check_while_typing,
7827 prefs_common.recheck_when_changing_dict,
7828 prefs_common.use_alternate,
7829 prefs_common.use_both_dicts,
7830 GTK_TEXT_VIEW(text),
7831 GTK_WINDOW(compose->window),
7832 compose_dict_changed,
7833 compose_spell_menu_changed,
7836 alertpanel_error(_("Spell checker could not "
7838 gtkaspell_checkers_strerror());
7839 gtkaspell_checkers_reset_error();
7841 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7845 compose->gtkaspell = gtkaspell;
7846 compose_spell_menu_changed(compose);
7847 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7850 compose_select_account(compose, account, TRUE);
7852 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7853 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7855 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7856 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7858 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7859 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7861 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7862 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7864 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7865 if (account->protocol != A_NNTP)
7866 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7867 prefs_common_translated_header_name("To:"));
7869 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7870 prefs_common_translated_header_name("Newsgroups:"));
7872 #ifndef USE_NEW_ADDRBOOK
7873 addressbook_set_target_compose(compose);
7875 if (mode != COMPOSE_REDIRECT)
7876 compose_set_template_menu(compose);
7878 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7881 compose_list = g_list_append(compose_list, compose);
7883 if (!prefs_common.show_ruler)
7884 gtk_widget_hide(ruler_hbox);
7886 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7889 compose->priority = PRIORITY_NORMAL;
7890 compose_update_priority_menu_item(compose);
7892 compose_set_out_encoding(compose);
7895 compose_update_actions_menu(compose);
7897 /* Privacy Systems menu */
7898 compose_update_privacy_systems_menu(compose);
7900 activate_privacy_system(compose, account, TRUE);
7901 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7903 gtk_widget_realize(window);
7905 gtk_widget_show(window);
7907 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7908 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7915 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7920 GtkWidget *optmenubox;
7923 GtkWidget *from_name = NULL;
7924 #if !(GTK_CHECK_VERSION(2,12,0))
7925 GtkTooltips *tips = compose->tooltips;
7928 gint num = 0, def_menu = 0;
7930 accounts = account_get_list();
7931 cm_return_val_if_fail(accounts != NULL, NULL);
7933 optmenubox = gtk_event_box_new();
7934 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7935 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7937 hbox = gtk_hbox_new(FALSE, 6);
7938 from_name = gtk_entry_new();
7940 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7941 G_CALLBACK(compose_grab_focus_cb), compose);
7943 for (; accounts != NULL; accounts = accounts->next, num++) {
7944 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7945 gchar *name, *from = NULL;
7947 if (ac == compose->account) def_menu = num;
7949 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7952 if (ac == compose->account) {
7953 if (ac->name && *ac->name) {
7955 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7956 from = g_strdup_printf("%s <%s>",
7958 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7960 from = g_strdup_printf("%s",
7962 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7965 COMBOBOX_ADD(menu, name, ac->account_id);
7970 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7972 g_signal_connect(G_OBJECT(optmenu), "changed",
7973 G_CALLBACK(account_activated),
7975 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7976 G_CALLBACK(compose_entry_popup_extend),
7979 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7980 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7982 CLAWS_SET_TIP(optmenubox,
7983 _("Account to use for this email"));
7984 CLAWS_SET_TIP(from_name,
7985 _("Sender address to be used"));
7987 compose->account_combo = optmenu;
7988 compose->from_name = from_name;
7993 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7995 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7996 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7997 Compose *compose = (Compose *) data;
7999 compose->priority = value;
8003 static void compose_reply_change_mode(Compose *compose,
8006 gboolean was_modified = compose->modified;
8008 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8010 cm_return_if_fail(compose->replyinfo != NULL);
8012 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8014 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8016 if (action == COMPOSE_REPLY_TO_ALL)
8018 if (action == COMPOSE_REPLY_TO_SENDER)
8020 if (action == COMPOSE_REPLY_TO_LIST)
8023 compose_remove_header_entries(compose);
8024 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8025 if (compose->account->set_autocc && compose->account->auto_cc)
8026 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8028 if (compose->account->set_autobcc && compose->account->auto_bcc)
8029 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8031 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8032 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8033 compose_show_first_last_header(compose, TRUE);
8034 compose->modified = was_modified;
8035 compose_set_title(compose);
8038 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8040 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8041 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8042 Compose *compose = (Compose *) data;
8045 compose_reply_change_mode(compose, value);
8048 static void compose_update_priority_menu_item(Compose * compose)
8050 GtkWidget *menuitem = NULL;
8051 switch (compose->priority) {
8052 case PRIORITY_HIGHEST:
8053 menuitem = gtk_ui_manager_get_widget
8054 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8057 menuitem = gtk_ui_manager_get_widget
8058 (compose->ui_manager, "/Menu/Options/Priority/High");
8060 case PRIORITY_NORMAL:
8061 menuitem = gtk_ui_manager_get_widget
8062 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8065 menuitem = gtk_ui_manager_get_widget
8066 (compose->ui_manager, "/Menu/Options/Priority/Low");
8068 case PRIORITY_LOWEST:
8069 menuitem = gtk_ui_manager_get_widget
8070 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8073 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8076 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8078 Compose *compose = (Compose *) data;
8080 gboolean can_sign = FALSE, can_encrypt = FALSE;
8082 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8084 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8087 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8088 g_free(compose->privacy_system);
8089 compose->privacy_system = NULL;
8090 if (systemid != NULL) {
8091 compose->privacy_system = g_strdup(systemid);
8093 can_sign = privacy_system_can_sign(systemid);
8094 can_encrypt = privacy_system_can_encrypt(systemid);
8097 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8099 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8100 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8103 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8105 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8106 GtkWidget *menuitem = NULL;
8107 GList *children, *amenu;
8108 gboolean can_sign = FALSE, can_encrypt = FALSE;
8109 gboolean found = FALSE;
8111 if (compose->privacy_system != NULL) {
8113 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8114 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8115 cm_return_if_fail(menuitem != NULL);
8117 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8120 while (amenu != NULL) {
8121 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8122 if (systemid != NULL) {
8123 if (strcmp(systemid, compose->privacy_system) == 0 &&
8124 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8125 menuitem = GTK_WIDGET(amenu->data);
8127 can_sign = privacy_system_can_sign(systemid);
8128 can_encrypt = privacy_system_can_encrypt(systemid);
8132 } else if (strlen(compose->privacy_system) == 0 &&
8133 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8134 menuitem = GTK_WIDGET(amenu->data);
8137 can_encrypt = FALSE;
8142 amenu = amenu->next;
8144 g_list_free(children);
8145 if (menuitem != NULL)
8146 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8148 if (warn && !found && strlen(compose->privacy_system)) {
8149 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8150 "will not be able to sign or encrypt this message."),
8151 compose->privacy_system);
8155 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8156 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8159 static void compose_set_out_encoding(Compose *compose)
8161 CharSet out_encoding;
8162 const gchar *branch = NULL;
8163 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8165 switch(out_encoding) {
8166 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8167 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8168 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8169 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8170 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8171 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8172 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8173 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8174 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8175 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8176 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8177 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8178 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8179 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8180 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8181 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8182 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8183 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8184 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8185 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8186 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8187 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8188 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8189 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8190 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8191 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8192 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8193 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8194 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8195 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8196 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8197 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8198 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8200 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8203 static void compose_set_template_menu(Compose *compose)
8205 GSList *tmpl_list, *cur;
8209 tmpl_list = template_get_config();
8211 menu = gtk_menu_new();
8213 gtk_menu_set_accel_group (GTK_MENU (menu),
8214 gtk_ui_manager_get_accel_group(compose->ui_manager));
8215 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8216 Template *tmpl = (Template *)cur->data;
8217 gchar *accel_path = NULL;
8218 item = gtk_menu_item_new_with_label(tmpl->name);
8219 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8220 g_signal_connect(G_OBJECT(item), "activate",
8221 G_CALLBACK(compose_template_activate_cb),
8223 g_object_set_data(G_OBJECT(item), "template", tmpl);
8224 gtk_widget_show(item);
8225 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8226 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8230 gtk_widget_show(menu);
8231 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8234 void compose_update_actions_menu(Compose *compose)
8236 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8239 static void compose_update_privacy_systems_menu(Compose *compose)
8241 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8242 GSList *systems, *cur;
8244 GtkWidget *system_none;
8246 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8247 GtkWidget *privacy_menu = gtk_menu_new();
8249 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8250 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8252 g_signal_connect(G_OBJECT(system_none), "activate",
8253 G_CALLBACK(compose_set_privacy_system_cb), compose);
8255 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8256 gtk_widget_show(system_none);
8258 systems = privacy_get_system_ids();
8259 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8260 gchar *systemid = cur->data;
8262 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8263 widget = gtk_radio_menu_item_new_with_label(group,
8264 privacy_system_get_name(systemid));
8265 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8266 g_strdup(systemid), g_free);
8267 g_signal_connect(G_OBJECT(widget), "activate",
8268 G_CALLBACK(compose_set_privacy_system_cb), compose);
8270 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8271 gtk_widget_show(widget);
8274 g_slist_free(systems);
8275 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8276 gtk_widget_show_all(privacy_menu);
8277 gtk_widget_show_all(privacy_menuitem);
8280 void compose_reflect_prefs_all(void)
8285 for (cur = compose_list; cur != NULL; cur = cur->next) {
8286 compose = (Compose *)cur->data;
8287 compose_set_template_menu(compose);
8291 void compose_reflect_prefs_pixmap_theme(void)
8296 for (cur = compose_list; cur != NULL; cur = cur->next) {
8297 compose = (Compose *)cur->data;
8298 toolbar_update(TOOLBAR_COMPOSE, compose);
8302 static const gchar *compose_quote_char_from_context(Compose *compose)
8304 const gchar *qmark = NULL;
8306 cm_return_val_if_fail(compose != NULL, NULL);
8308 switch (compose->mode) {
8309 /* use forward-specific quote char */
8310 case COMPOSE_FORWARD:
8311 case COMPOSE_FORWARD_AS_ATTACH:
8312 case COMPOSE_FORWARD_INLINE:
8313 if (compose->folder && compose->folder->prefs &&
8314 compose->folder->prefs->forward_with_format)
8315 qmark = compose->folder->prefs->forward_quotemark;
8316 else if (compose->account->forward_with_format)
8317 qmark = compose->account->forward_quotemark;
8319 qmark = prefs_common.fw_quotemark;
8322 /* use reply-specific quote char in all other modes */
8324 if (compose->folder && compose->folder->prefs &&
8325 compose->folder->prefs->reply_with_format)
8326 qmark = compose->folder->prefs->reply_quotemark;
8327 else if (compose->account->reply_with_format)
8328 qmark = compose->account->reply_quotemark;
8330 qmark = prefs_common.quotemark;
8334 if (qmark == NULL || *qmark == '\0')
8340 static void compose_template_apply(Compose *compose, Template *tmpl,
8344 GtkTextBuffer *buffer;
8348 gchar *parsed_str = NULL;
8349 gint cursor_pos = 0;
8350 const gchar *err_msg = _("The body of the template has an error at line %d.");
8353 /* process the body */
8355 text = GTK_TEXT_VIEW(compose->text);
8356 buffer = gtk_text_view_get_buffer(text);
8359 qmark = compose_quote_char_from_context(compose);
8361 if (compose->replyinfo != NULL) {
8364 gtk_text_buffer_set_text(buffer, "", -1);
8365 mark = gtk_text_buffer_get_insert(buffer);
8366 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8368 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8369 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8371 } else if (compose->fwdinfo != NULL) {
8374 gtk_text_buffer_set_text(buffer, "", -1);
8375 mark = gtk_text_buffer_get_insert(buffer);
8376 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8378 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8379 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8382 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8384 GtkTextIter start, end;
8387 gtk_text_buffer_get_start_iter(buffer, &start);
8388 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8389 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8391 /* clear the buffer now */
8393 gtk_text_buffer_set_text(buffer, "", -1);
8395 parsed_str = compose_quote_fmt(compose, dummyinfo,
8396 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8397 procmsg_msginfo_free( dummyinfo );
8403 gtk_text_buffer_set_text(buffer, "", -1);
8404 mark = gtk_text_buffer_get_insert(buffer);
8405 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8408 if (replace && parsed_str && compose->account->auto_sig)
8409 compose_insert_sig(compose, FALSE);
8411 if (replace && parsed_str) {
8412 gtk_text_buffer_get_start_iter(buffer, &iter);
8413 gtk_text_buffer_place_cursor(buffer, &iter);
8417 cursor_pos = quote_fmt_get_cursor_pos();
8418 compose->set_cursor_pos = cursor_pos;
8419 if (cursor_pos == -1)
8421 gtk_text_buffer_get_start_iter(buffer, &iter);
8422 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8423 gtk_text_buffer_place_cursor(buffer, &iter);
8426 /* process the other fields */
8428 compose_template_apply_fields(compose, tmpl);
8429 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8430 quote_fmt_reset_vartable();
8431 compose_changed_cb(NULL, compose);
8434 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8435 gtkaspell_highlight_all(compose->gtkaspell);
8439 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8441 MsgInfo* dummyinfo = NULL;
8442 MsgInfo *msginfo = NULL;
8445 if (compose->replyinfo != NULL)
8446 msginfo = compose->replyinfo;
8447 else if (compose->fwdinfo != NULL)
8448 msginfo = compose->fwdinfo;
8450 dummyinfo = compose_msginfo_new_from_compose(compose);
8451 msginfo = dummyinfo;
8454 if (tmpl->from && *tmpl->from != '\0') {
8456 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8457 compose->gtkaspell);
8459 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8461 quote_fmt_scan_string(tmpl->from);
8464 buf = quote_fmt_get_buffer();
8466 alertpanel_error(_("Template From format error."));
8468 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8472 if (tmpl->to && *tmpl->to != '\0') {
8474 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8475 compose->gtkaspell);
8477 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8479 quote_fmt_scan_string(tmpl->to);
8482 buf = quote_fmt_get_buffer();
8484 alertpanel_error(_("Template To format error."));
8486 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8490 if (tmpl->cc && *tmpl->cc != '\0') {
8492 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8493 compose->gtkaspell);
8495 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8497 quote_fmt_scan_string(tmpl->cc);
8500 buf = quote_fmt_get_buffer();
8502 alertpanel_error(_("Template Cc format error."));
8504 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8508 if (tmpl->bcc && *tmpl->bcc != '\0') {
8510 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8511 compose->gtkaspell);
8513 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8515 quote_fmt_scan_string(tmpl->bcc);
8518 buf = quote_fmt_get_buffer();
8520 alertpanel_error(_("Template Bcc format error."));
8522 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8526 /* process the subject */
8527 if (tmpl->subject && *tmpl->subject != '\0') {
8529 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8530 compose->gtkaspell);
8532 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8534 quote_fmt_scan_string(tmpl->subject);
8537 buf = quote_fmt_get_buffer();
8539 alertpanel_error(_("Template subject format error."));
8541 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8545 procmsg_msginfo_free( dummyinfo );
8548 static void compose_destroy(Compose *compose)
8550 GtkAllocation allocation;
8551 GtkTextBuffer *buffer;
8552 GtkClipboard *clipboard;
8554 compose_list = g_list_remove(compose_list, compose);
8556 if (compose->updating) {
8557 debug_print("danger, not destroying anything now\n");
8558 compose->deferred_destroy = TRUE;
8561 /* NOTE: address_completion_end() does nothing with the window
8562 * however this may change. */
8563 address_completion_end(compose->window);
8565 slist_free_strings_full(compose->to_list);
8566 slist_free_strings_full(compose->newsgroup_list);
8567 slist_free_strings_full(compose->header_list);
8569 slist_free_strings_full(extra_headers);
8570 extra_headers = NULL;
8572 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8574 g_hash_table_destroy(compose->email_hashtable);
8576 procmsg_msginfo_free(compose->targetinfo);
8577 procmsg_msginfo_free(compose->replyinfo);
8578 procmsg_msginfo_free(compose->fwdinfo);
8580 g_free(compose->replyto);
8581 g_free(compose->cc);
8582 g_free(compose->bcc);
8583 g_free(compose->newsgroups);
8584 g_free(compose->followup_to);
8586 g_free(compose->ml_post);
8588 g_free(compose->inreplyto);
8589 g_free(compose->references);
8590 g_free(compose->msgid);
8591 g_free(compose->boundary);
8593 g_free(compose->redirect_filename);
8594 if (compose->undostruct)
8595 undo_destroy(compose->undostruct);
8597 g_free(compose->sig_str);
8599 g_free(compose->exteditor_file);
8601 g_free(compose->orig_charset);
8603 g_free(compose->privacy_system);
8605 #ifndef USE_NEW_ADDRBOOK
8606 if (addressbook_get_target_compose() == compose)
8607 addressbook_set_target_compose(NULL);
8610 if (compose->gtkaspell) {
8611 gtkaspell_delete(compose->gtkaspell);
8612 compose->gtkaspell = NULL;
8616 if (!compose->batch) {
8617 gtk_widget_get_allocation(compose->window, &allocation);
8618 prefs_common.compose_width = allocation.width;
8619 prefs_common.compose_height = allocation.height;
8622 if (!gtk_widget_get_parent(compose->paned))
8623 gtk_widget_destroy(compose->paned);
8624 gtk_widget_destroy(compose->popupmenu);
8626 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8627 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8628 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8630 gtk_widget_destroy(compose->window);
8631 toolbar_destroy(compose->toolbar);
8632 g_free(compose->toolbar);
8633 cm_mutex_free(compose->mutex);
8637 static void compose_attach_info_free(AttachInfo *ainfo)
8639 g_free(ainfo->file);
8640 g_free(ainfo->content_type);
8641 g_free(ainfo->name);
8642 g_free(ainfo->charset);
8646 static void compose_attach_update_label(Compose *compose)
8651 GtkTreeModel *model;
8656 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8657 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8658 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8662 while(gtk_tree_model_iter_next(model, &iter))
8665 text = g_strdup_printf("(%d)", i);
8666 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8670 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8672 Compose *compose = (Compose *)data;
8673 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8674 GtkTreeSelection *selection;
8676 GtkTreeModel *model;
8678 selection = gtk_tree_view_get_selection(tree_view);
8679 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8684 for (cur = sel; cur != NULL; cur = cur->next) {
8685 GtkTreePath *path = cur->data;
8686 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8689 gtk_tree_path_free(path);
8692 for (cur = sel; cur != NULL; cur = cur->next) {
8693 GtkTreeRowReference *ref = cur->data;
8694 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8697 if (gtk_tree_model_get_iter(model, &iter, path))
8698 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8700 gtk_tree_path_free(path);
8701 gtk_tree_row_reference_free(ref);
8705 compose_attach_update_label(compose);
8708 static struct _AttachProperty
8711 GtkWidget *mimetype_entry;
8712 GtkWidget *encoding_optmenu;
8713 GtkWidget *path_entry;
8714 GtkWidget *filename_entry;
8716 GtkWidget *cancel_btn;
8719 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8721 gtk_tree_path_free((GtkTreePath *)ptr);
8724 static void compose_attach_property(GtkAction *action, gpointer data)
8726 Compose *compose = (Compose *)data;
8727 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8729 GtkComboBox *optmenu;
8730 GtkTreeSelection *selection;
8732 GtkTreeModel *model;
8735 static gboolean cancelled;
8737 /* only if one selected */
8738 selection = gtk_tree_view_get_selection(tree_view);
8739 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8742 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8746 path = (GtkTreePath *) sel->data;
8747 gtk_tree_model_get_iter(model, &iter, path);
8748 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8751 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8757 if (!attach_prop.window)
8758 compose_attach_property_create(&cancelled);
8759 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8760 gtk_widget_grab_focus(attach_prop.ok_btn);
8761 gtk_widget_show(attach_prop.window);
8762 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8763 GTK_WINDOW(compose->window));
8765 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8766 if (ainfo->encoding == ENC_UNKNOWN)
8767 combobox_select_by_data(optmenu, ENC_BASE64);
8769 combobox_select_by_data(optmenu, ainfo->encoding);
8771 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8772 ainfo->content_type ? ainfo->content_type : "");
8773 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8774 ainfo->file ? ainfo->file : "");
8775 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8776 ainfo->name ? ainfo->name : "");
8779 const gchar *entry_text;
8781 gchar *cnttype = NULL;
8788 gtk_widget_hide(attach_prop.window);
8789 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8794 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8795 if (*entry_text != '\0') {
8798 text = g_strstrip(g_strdup(entry_text));
8799 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8800 cnttype = g_strdup(text);
8803 alertpanel_error(_("Invalid MIME type."));
8809 ainfo->encoding = combobox_get_active_data(optmenu);
8811 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8812 if (*entry_text != '\0') {
8813 if (is_file_exist(entry_text) &&
8814 (size = get_file_size(entry_text)) > 0)
8815 file = g_strdup(entry_text);
8818 (_("File doesn't exist or is empty."));
8824 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8825 if (*entry_text != '\0') {
8826 g_free(ainfo->name);
8827 ainfo->name = g_strdup(entry_text);
8831 g_free(ainfo->content_type);
8832 ainfo->content_type = cnttype;
8835 g_free(ainfo->file);
8839 ainfo->size = (goffset)size;
8841 /* update tree store */
8842 text = to_human_readable(ainfo->size);
8843 gtk_tree_model_get_iter(model, &iter, path);
8844 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8845 COL_MIMETYPE, ainfo->content_type,
8847 COL_NAME, ainfo->name,
8848 COL_CHARSET, ainfo->charset,
8854 gtk_tree_path_free(path);
8857 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8859 label = gtk_label_new(str); \
8860 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8861 GTK_FILL, 0, 0, 0); \
8862 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8864 entry = gtk_entry_new(); \
8865 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8866 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8869 static void compose_attach_property_create(gboolean *cancelled)
8875 GtkWidget *mimetype_entry;
8878 GtkListStore *optmenu_menu;
8879 GtkWidget *path_entry;
8880 GtkWidget *filename_entry;
8883 GtkWidget *cancel_btn;
8884 GList *mime_type_list, *strlist;
8887 debug_print("Creating attach_property window...\n");
8889 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8890 gtk_widget_set_size_request(window, 480, -1);
8891 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8892 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8893 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8894 g_signal_connect(G_OBJECT(window), "delete_event",
8895 G_CALLBACK(attach_property_delete_event),
8897 g_signal_connect(G_OBJECT(window), "key_press_event",
8898 G_CALLBACK(attach_property_key_pressed),
8901 vbox = gtk_vbox_new(FALSE, 8);
8902 gtk_container_add(GTK_CONTAINER(window), vbox);
8904 table = gtk_table_new(4, 2, FALSE);
8905 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8906 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8907 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8909 label = gtk_label_new(_("MIME type"));
8910 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8912 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8913 #if !GTK_CHECK_VERSION(2, 24, 0)
8914 mimetype_entry = gtk_combo_box_entry_new_text();
8916 mimetype_entry = gtk_combo_box_text_new_with_entry();
8918 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8919 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8921 /* stuff with list */
8922 mime_type_list = procmime_get_mime_type_list();
8924 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8925 MimeType *type = (MimeType *) mime_type_list->data;
8928 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8930 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8933 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8934 (GCompareFunc)strcmp2);
8937 for (mime_type_list = strlist; mime_type_list != NULL;
8938 mime_type_list = mime_type_list->next) {
8939 #if !GTK_CHECK_VERSION(2, 24, 0)
8940 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8942 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8944 g_free(mime_type_list->data);
8946 g_list_free(strlist);
8947 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8948 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8950 label = gtk_label_new(_("Encoding"));
8951 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8953 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8955 hbox = gtk_hbox_new(FALSE, 0);
8956 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8957 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8959 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8960 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8962 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8963 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8964 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8965 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8966 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8968 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8970 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8971 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8973 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8974 &ok_btn, GTK_STOCK_OK,
8976 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8977 gtk_widget_grab_default(ok_btn);
8979 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8980 G_CALLBACK(attach_property_ok),
8982 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8983 G_CALLBACK(attach_property_cancel),
8986 gtk_widget_show_all(vbox);
8988 attach_prop.window = window;
8989 attach_prop.mimetype_entry = mimetype_entry;
8990 attach_prop.encoding_optmenu = optmenu;
8991 attach_prop.path_entry = path_entry;
8992 attach_prop.filename_entry = filename_entry;
8993 attach_prop.ok_btn = ok_btn;
8994 attach_prop.cancel_btn = cancel_btn;
8997 #undef SET_LABEL_AND_ENTRY
8999 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
9005 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9011 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9012 gboolean *cancelled)
9020 static gboolean attach_property_key_pressed(GtkWidget *widget,
9022 gboolean *cancelled)
9024 if (event && event->keyval == GDK_KEY_Escape) {
9028 if (event && event->keyval == GDK_KEY_Return) {
9036 static void compose_exec_ext_editor(Compose *compose)
9043 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9044 G_DIR_SEPARATOR, compose);
9046 if (pipe(pipe_fds) < 0) {
9052 if ((pid = fork()) < 0) {
9059 /* close the write side of the pipe */
9062 compose->exteditor_file = g_strdup(tmp);
9063 compose->exteditor_pid = pid;
9065 compose_set_ext_editor_sensitive(compose, FALSE);
9068 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9070 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9072 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9076 } else { /* process-monitoring process */
9082 /* close the read side of the pipe */
9085 if (compose_write_body_to_file(compose, tmp) < 0) {
9086 fd_write_all(pipe_fds[1], "2\n", 2);
9090 pid_ed = compose_exec_ext_editor_real(tmp);
9092 fd_write_all(pipe_fds[1], "1\n", 2);
9096 /* wait until editor is terminated */
9097 waitpid(pid_ed, NULL, 0);
9099 fd_write_all(pipe_fds[1], "0\n", 2);
9106 #endif /* G_OS_UNIX */
9110 static gint compose_exec_ext_editor_real(const gchar *file)
9117 cm_return_val_if_fail(file != NULL, -1);
9119 if ((pid = fork()) < 0) {
9124 if (pid != 0) return pid;
9126 /* grandchild process */
9128 if (setpgid(0, getppid()))
9131 if (prefs_common_get_ext_editor_cmd() &&
9132 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9133 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9134 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9136 if (prefs_common_get_ext_editor_cmd())
9137 g_warning("External editor command-line is invalid: '%s'\n",
9138 prefs_common_get_ext_editor_cmd());
9139 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9142 cmdline = strsplit_with_quote(buf, " ", 1024);
9143 execvp(cmdline[0], cmdline);
9146 g_strfreev(cmdline);
9151 static gboolean compose_ext_editor_kill(Compose *compose)
9153 pid_t pgid = compose->exteditor_pid * -1;
9156 ret = kill(pgid, 0);
9158 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9162 msg = g_strdup_printf
9163 (_("The external editor is still working.\n"
9164 "Force terminating the process?\n"
9165 "process group id: %d"), -pgid);
9166 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9167 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9171 if (val == G_ALERTALTERNATE) {
9172 g_source_remove(compose->exteditor_tag);
9173 g_io_channel_shutdown(compose->exteditor_ch,
9175 g_io_channel_unref(compose->exteditor_ch);
9177 if (kill(pgid, SIGTERM) < 0) perror("kill");
9178 waitpid(compose->exteditor_pid, NULL, 0);
9180 g_warning("Terminated process group id: %d", -pgid);
9181 g_warning("Temporary file: %s",
9182 compose->exteditor_file);
9184 compose_set_ext_editor_sensitive(compose, TRUE);
9186 g_free(compose->exteditor_file);
9187 compose->exteditor_file = NULL;
9188 compose->exteditor_pid = -1;
9189 compose->exteditor_ch = NULL;
9190 compose->exteditor_tag = -1;
9198 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9202 Compose *compose = (Compose *)data;
9205 debug_print("Compose: input from monitoring process\n");
9207 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9209 g_io_channel_shutdown(source, FALSE, NULL);
9210 g_io_channel_unref(source);
9212 waitpid(compose->exteditor_pid, NULL, 0);
9214 if (buf[0] == '0') { /* success */
9215 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9216 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9218 gtk_text_buffer_set_text(buffer, "", -1);
9219 compose_insert_file(compose, compose->exteditor_file);
9220 compose_changed_cb(NULL, compose);
9221 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9223 if (claws_unlink(compose->exteditor_file) < 0)
9224 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9225 } else if (buf[0] == '1') { /* failed */
9226 g_warning("Couldn't exec external editor\n");
9227 if (claws_unlink(compose->exteditor_file) < 0)
9228 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9229 } else if (buf[0] == '2') {
9230 g_warning("Couldn't write to file\n");
9231 } else if (buf[0] == '3') {
9232 g_warning("Pipe read failed\n");
9235 compose_set_ext_editor_sensitive(compose, TRUE);
9237 g_free(compose->exteditor_file);
9238 compose->exteditor_file = NULL;
9239 compose->exteditor_pid = -1;
9240 compose->exteditor_ch = NULL;
9241 compose->exteditor_tag = -1;
9246 static void compose_set_ext_editor_sensitive(Compose *compose,
9249 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9250 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9251 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9252 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9253 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9254 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9255 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9257 gtk_widget_set_sensitive(compose->text, sensitive);
9258 if (compose->toolbar->send_btn)
9259 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9260 if (compose->toolbar->sendl_btn)
9261 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9262 if (compose->toolbar->draft_btn)
9263 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9264 if (compose->toolbar->insert_btn)
9265 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9266 if (compose->toolbar->sig_btn)
9267 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9268 if (compose->toolbar->exteditor_btn)
9269 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9270 if (compose->toolbar->linewrap_current_btn)
9271 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9272 if (compose->toolbar->linewrap_all_btn)
9273 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9275 #endif /* G_OS_UNIX */
9278 * compose_undo_state_changed:
9280 * Change the sensivity of the menuentries undo and redo
9282 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9283 gint redo_state, gpointer data)
9285 Compose *compose = (Compose *)data;
9287 switch (undo_state) {
9288 case UNDO_STATE_TRUE:
9289 if (!undostruct->undo_state) {
9290 undostruct->undo_state = TRUE;
9291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9294 case UNDO_STATE_FALSE:
9295 if (undostruct->undo_state) {
9296 undostruct->undo_state = FALSE;
9297 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9300 case UNDO_STATE_UNCHANGED:
9302 case UNDO_STATE_REFRESH:
9303 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9306 g_warning("Undo state not recognized");
9310 switch (redo_state) {
9311 case UNDO_STATE_TRUE:
9312 if (!undostruct->redo_state) {
9313 undostruct->redo_state = TRUE;
9314 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9317 case UNDO_STATE_FALSE:
9318 if (undostruct->redo_state) {
9319 undostruct->redo_state = FALSE;
9320 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9323 case UNDO_STATE_UNCHANGED:
9325 case UNDO_STATE_REFRESH:
9326 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9329 g_warning("Redo state not recognized");
9334 /* callback functions */
9336 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9337 GtkAllocation *allocation,
9340 prefs_common.compose_notebook_height = allocation->height;
9343 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9344 * includes "non-client" (windows-izm) in calculation, so this calculation
9345 * may not be accurate.
9347 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9348 GtkAllocation *allocation,
9349 GtkSHRuler *shruler)
9351 if (prefs_common.show_ruler) {
9352 gint char_width = 0, char_height = 0;
9353 gint line_width_in_chars;
9355 gtkut_get_font_size(GTK_WIDGET(widget),
9356 &char_width, &char_height);
9357 line_width_in_chars =
9358 (allocation->width - allocation->x) / char_width;
9360 /* got the maximum */
9361 gtk_shruler_set_range(GTK_SHRULER(shruler),
9362 0.0, line_width_in_chars, 0);
9371 ComposePrefType type;
9372 gboolean entry_marked;
9375 static void account_activated(GtkComboBox *optmenu, gpointer data)
9377 Compose *compose = (Compose *)data;
9380 gchar *folderidentifier;
9381 gint account_id = 0;
9384 GSList *list, *saved_list = NULL;
9385 HeaderEntryState *state;
9386 GtkRcStyle *style = NULL;
9387 #if !GTK_CHECK_VERSION(3, 0, 0)
9388 static GdkColor yellow;
9389 static gboolean color_set = FALSE;
9391 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9394 /* Get ID of active account in the combo box */
9395 menu = gtk_combo_box_get_model(optmenu);
9396 gtk_combo_box_get_active_iter(optmenu, &iter);
9397 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9399 ac = account_find_from_id(account_id);
9400 cm_return_if_fail(ac != NULL);
9402 if (ac != compose->account) {
9403 compose_select_account(compose, ac, FALSE);
9405 for (list = compose->header_list; list; list = list->next) {
9406 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9408 if (hentry->type == PREF_ACCOUNT || !list->next) {
9409 compose_destroy_headerentry(compose, hentry);
9413 state = g_malloc0(sizeof(HeaderEntryState));
9414 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9415 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9416 state->entry = gtk_editable_get_chars(
9417 GTK_EDITABLE(hentry->entry), 0, -1);
9418 state->type = hentry->type;
9420 #if !GTK_CHECK_VERSION(3, 0, 0)
9422 gdk_color_parse("#f5f6be", &yellow);
9423 color_set = gdk_colormap_alloc_color(
9424 gdk_colormap_get_system(),
9425 &yellow, FALSE, TRUE);
9429 style = gtk_widget_get_modifier_style(hentry->entry);
9430 state->entry_marked = gdk_color_equal(&yellow,
9431 &style->base[GTK_STATE_NORMAL]);
9433 saved_list = g_slist_append(saved_list, state);
9434 compose_destroy_headerentry(compose, hentry);
9437 compose->header_last = NULL;
9438 g_slist_free(compose->header_list);
9439 compose->header_list = NULL;
9440 compose->header_nextrow = 1;
9441 compose_create_header_entry(compose);
9443 if (ac->set_autocc && ac->auto_cc)
9444 compose_entry_append(compose, ac->auto_cc,
9445 COMPOSE_CC, PREF_ACCOUNT);
9447 if (ac->set_autobcc && ac->auto_bcc)
9448 compose_entry_append(compose, ac->auto_bcc,
9449 COMPOSE_BCC, PREF_ACCOUNT);
9451 if (ac->set_autoreplyto && ac->auto_replyto)
9452 compose_entry_append(compose, ac->auto_replyto,
9453 COMPOSE_REPLYTO, PREF_ACCOUNT);
9455 for (list = saved_list; list; list = list->next) {
9456 state = (HeaderEntryState *) list->data;
9458 compose_add_header_entry(compose, state->header,
9459 state->entry, state->type);
9460 if (state->entry_marked)
9461 compose_entry_mark_default_to(compose, state->entry);
9463 g_free(state->header);
9464 g_free(state->entry);
9467 g_slist_free(saved_list);
9469 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9470 (ac->protocol == A_NNTP) ?
9471 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9474 /* Set message save folder */
9475 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9476 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9478 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9479 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9481 compose_set_save_to(compose, NULL);
9482 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9483 folderidentifier = folder_item_get_identifier(account_get_special_folder
9484 (compose->account, F_OUTBOX));
9485 compose_set_save_to(compose, folderidentifier);
9486 g_free(folderidentifier);
9490 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9491 GtkTreeViewColumn *column, Compose *compose)
9493 compose_attach_property(NULL, compose);
9496 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9499 Compose *compose = (Compose *)data;
9500 GtkTreeSelection *attach_selection;
9501 gint attach_nr_selected;
9503 if (!event) return FALSE;
9505 if (event->button == 3) {
9506 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9507 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9509 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9510 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9512 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9513 NULL, NULL, event->button, event->time);
9520 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9523 Compose *compose = (Compose *)data;
9525 if (!event) return FALSE;
9527 switch (event->keyval) {
9528 case GDK_KEY_Delete:
9529 compose_attach_remove_selected(NULL, compose);
9535 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9537 toolbar_comp_set_sensitive(compose, allow);
9538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9539 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9541 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9543 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9544 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9545 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9547 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9551 static void compose_send_cb(GtkAction *action, gpointer data)
9553 Compose *compose = (Compose *)data;
9555 if (prefs_common.work_offline &&
9556 !inc_offline_should_override(TRUE,
9557 _("Claws Mail needs network access in order "
9558 "to send this email.")))
9561 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9562 g_source_remove(compose->draft_timeout_tag);
9563 compose->draft_timeout_tag = -1;
9566 compose_send(compose);
9569 static void compose_send_later_cb(GtkAction *action, gpointer data)
9571 Compose *compose = (Compose *)data;
9575 compose_allow_user_actions(compose, FALSE);
9576 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9577 compose_allow_user_actions(compose, TRUE);
9581 compose_close(compose);
9582 } else if (val == -1) {
9583 alertpanel_error(_("Could not queue message."));
9584 } else if (val == -2) {
9585 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9586 } else if (val == -3) {
9587 if (privacy_peek_error())
9588 alertpanel_error(_("Could not queue message for sending:\n\n"
9589 "Signature failed: %s"), privacy_get_error());
9590 } else if (val == -4) {
9591 alertpanel_error(_("Could not queue message for sending:\n\n"
9592 "Charset conversion failed."));
9593 } else if (val == -5) {
9594 alertpanel_error(_("Could not queue message for sending:\n\n"
9595 "Couldn't get recipient encryption key."));
9596 } else if (val == -6) {
9599 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9602 #define DRAFTED_AT_EXIT "drafted_at_exit"
9603 static void compose_register_draft(MsgInfo *info)
9605 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9606 DRAFTED_AT_EXIT, NULL);
9607 FILE *fp = g_fopen(filepath, "ab");
9610 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9618 gboolean compose_draft (gpointer data, guint action)
9620 Compose *compose = (Compose *)data;
9625 MsgFlags flag = {0, 0};
9626 static gboolean lock = FALSE;
9627 MsgInfo *newmsginfo;
9629 gboolean target_locked = FALSE;
9630 gboolean err = FALSE;
9632 if (lock) return FALSE;
9634 if (compose->sending)
9637 draft = account_get_special_folder(compose->account, F_DRAFT);
9638 cm_return_val_if_fail(draft != NULL, FALSE);
9640 if (!g_mutex_trylock(compose->mutex)) {
9641 /* we don't want to lock the mutex once it's available,
9642 * because as the only other part of compose.c locking
9643 * it is compose_close - which means once unlocked,
9644 * the compose struct will be freed */
9645 debug_print("couldn't lock mutex, probably sending\n");
9651 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9652 G_DIR_SEPARATOR, compose);
9653 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9654 FILE_OP_ERROR(tmp, "fopen");
9658 /* chmod for security */
9659 if (change_file_mode_rw(fp, tmp) < 0) {
9660 FILE_OP_ERROR(tmp, "chmod");
9661 g_warning("can't change file mode\n");
9664 /* Save draft infos */
9665 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9666 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9668 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9669 gchar *savefolderid;
9671 savefolderid = compose_get_save_to(compose);
9672 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9673 g_free(savefolderid);
9675 if (compose->return_receipt) {
9676 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9678 if (compose->privacy_system) {
9679 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9680 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9681 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9684 /* Message-ID of message replying to */
9685 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9688 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9689 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9692 /* Message-ID of message forwarding to */
9693 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9696 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9697 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9701 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9702 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9704 sheaders = compose_get_manual_headers_info(compose);
9705 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9708 /* end of headers */
9709 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9716 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9720 if (fclose(fp) == EOF) {
9724 if (compose->targetinfo) {
9725 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9726 flag.perm_flags = target_locked?MSG_LOCKED:0;
9728 flag.tmp_flags = MSG_DRAFT;
9730 folder_item_scan(draft);
9731 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9732 MsgInfo *tmpinfo = NULL;
9733 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9734 if (compose->msgid) {
9735 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9738 msgnum = tmpinfo->msgnum;
9739 procmsg_msginfo_free(tmpinfo);
9740 debug_print("got draft msgnum %d from scanning\n", msgnum);
9742 debug_print("didn't get draft msgnum after scanning\n");
9745 debug_print("got draft msgnum %d from adding\n", msgnum);
9751 if (action != COMPOSE_AUTO_SAVE) {
9752 if (action != COMPOSE_DRAFT_FOR_EXIT)
9753 alertpanel_error(_("Could not save draft."));
9756 gtkut_window_popup(compose->window);
9757 val = alertpanel_full(_("Could not save draft"),
9758 _("Could not save draft.\n"
9759 "Do you want to cancel exit or discard this email?"),
9760 _("_Cancel exit"), _("_Discard email"), NULL,
9761 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9762 if (val == G_ALERTALTERNATE) {
9764 g_mutex_unlock(compose->mutex); /* must be done before closing */
9765 compose_close(compose);
9769 g_mutex_unlock(compose->mutex); /* must be done before closing */
9778 if (compose->mode == COMPOSE_REEDIT) {
9779 compose_remove_reedit_target(compose, TRUE);
9782 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9785 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9787 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9789 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9790 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9791 procmsg_msginfo_set_flags(newmsginfo, 0,
9792 MSG_HAS_ATTACHMENT);
9794 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9795 compose_register_draft(newmsginfo);
9797 procmsg_msginfo_free(newmsginfo);
9800 folder_item_scan(draft);
9802 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9804 g_mutex_unlock(compose->mutex); /* must be done before closing */
9805 compose_close(compose);
9811 path = folder_item_fetch_msg(draft, msgnum);
9813 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9816 if (g_stat(path, &s) < 0) {
9817 FILE_OP_ERROR(path, "stat");
9823 procmsg_msginfo_free(compose->targetinfo);
9824 compose->targetinfo = procmsg_msginfo_new();
9825 compose->targetinfo->msgnum = msgnum;
9826 compose->targetinfo->size = (goffset)s.st_size;
9827 compose->targetinfo->mtime = s.st_mtime;
9828 compose->targetinfo->folder = draft;
9830 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9831 compose->mode = COMPOSE_REEDIT;
9833 if (action == COMPOSE_AUTO_SAVE) {
9834 compose->autosaved_draft = compose->targetinfo;
9836 compose->modified = FALSE;
9837 compose_set_title(compose);
9841 g_mutex_unlock(compose->mutex);
9845 void compose_clear_exit_drafts(void)
9847 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9848 DRAFTED_AT_EXIT, NULL);
9849 if (is_file_exist(filepath))
9850 claws_unlink(filepath);
9855 void compose_reopen_exit_drafts(void)
9857 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9858 DRAFTED_AT_EXIT, NULL);
9859 FILE *fp = g_fopen(filepath, "rb");
9863 while (fgets(buf, sizeof(buf), fp)) {
9864 gchar **parts = g_strsplit(buf, "\t", 2);
9865 const gchar *folder = parts[0];
9866 int msgnum = parts[1] ? atoi(parts[1]):-1;
9868 if (folder && *folder && msgnum > -1) {
9869 FolderItem *item = folder_find_item_from_identifier(folder);
9870 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9872 compose_reedit(info, FALSE);
9879 compose_clear_exit_drafts();
9882 static void compose_save_cb(GtkAction *action, gpointer data)
9884 Compose *compose = (Compose *)data;
9885 compose_draft(compose, COMPOSE_KEEP_EDITING);
9886 compose->rmode = COMPOSE_REEDIT;
9889 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9891 if (compose && file_list) {
9894 for ( tmp = file_list; tmp; tmp = tmp->next) {
9895 gchar *file = (gchar *) tmp->data;
9896 gchar *utf8_filename = conv_filename_to_utf8(file);
9897 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9898 compose_changed_cb(NULL, compose);
9903 g_free(utf8_filename);
9908 static void compose_attach_cb(GtkAction *action, gpointer data)
9910 Compose *compose = (Compose *)data;
9913 if (compose->redirect_filename != NULL)
9916 /* Set focus_window properly, in case we were called via popup menu,
9917 * which unsets it (via focus_out_event callback on compose window). */
9918 manage_window_focus_in(compose->window, NULL, NULL);
9920 file_list = filesel_select_multiple_files_open(_("Select file"));
9923 compose_attach_from_list(compose, file_list, TRUE);
9924 g_list_free(file_list);
9928 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9930 Compose *compose = (Compose *)data;
9932 gint files_inserted = 0;
9934 file_list = filesel_select_multiple_files_open(_("Select file"));
9939 for ( tmp = file_list; tmp; tmp = tmp->next) {
9940 gchar *file = (gchar *) tmp->data;
9941 gchar *filedup = g_strdup(file);
9942 gchar *shortfile = g_path_get_basename(filedup);
9943 ComposeInsertResult res;
9944 /* insert the file if the file is short or if the user confirmed that
9945 he/she wants to insert the large file */
9946 res = compose_insert_file(compose, file);
9947 if (res == COMPOSE_INSERT_READ_ERROR) {
9948 alertpanel_error(_("File '%s' could not be read."), shortfile);
9949 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9950 alertpanel_error(_("File '%s' contained invalid characters\n"
9951 "for the current encoding, insertion may be incorrect."),
9953 } else if (res == COMPOSE_INSERT_SUCCESS)
9960 g_list_free(file_list);
9964 if (files_inserted > 0 && compose->gtkaspell &&
9965 compose->gtkaspell->check_while_typing)
9966 gtkaspell_highlight_all(compose->gtkaspell);
9970 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9972 Compose *compose = (Compose *)data;
9974 compose_insert_sig(compose, FALSE);
9977 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9981 Compose *compose = (Compose *)data;
9983 gtkut_widget_get_uposition(widget, &x, &y);
9984 if (!compose->batch) {
9985 prefs_common.compose_x = x;
9986 prefs_common.compose_y = y;
9988 if (compose->sending || compose->updating)
9990 compose_close_cb(NULL, compose);
9994 void compose_close_toolbar(Compose *compose)
9996 compose_close_cb(NULL, compose);
9999 static void compose_close_cb(GtkAction *action, gpointer data)
10001 Compose *compose = (Compose *)data;
10005 if (compose->exteditor_tag != -1) {
10006 if (!compose_ext_editor_kill(compose))
10011 if (compose->modified) {
10012 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10013 if (!g_mutex_trylock(compose->mutex)) {
10014 /* we don't want to lock the mutex once it's available,
10015 * because as the only other part of compose.c locking
10016 * it is compose_close - which means once unlocked,
10017 * the compose struct will be freed */
10018 debug_print("couldn't lock mutex, probably sending\n");
10022 val = alertpanel(_("Discard message"),
10023 _("This message has been modified. Discard it?"),
10024 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10026 val = alertpanel(_("Save changes"),
10027 _("This message has been modified. Save the latest changes?"),
10028 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10030 g_mutex_unlock(compose->mutex);
10032 case G_ALERTDEFAULT:
10033 if (prefs_common.autosave && !reedit)
10034 compose_remove_draft(compose);
10036 case G_ALERTALTERNATE:
10037 compose_draft(data, COMPOSE_QUIT_EDITING);
10044 compose_close(compose);
10047 static void compose_print_cb(GtkAction *action, gpointer data)
10049 Compose *compose = (Compose *) data;
10051 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10052 if (compose->targetinfo)
10053 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10056 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10058 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10059 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10060 Compose *compose = (Compose *) data;
10063 compose->out_encoding = (CharSet)value;
10066 static void compose_address_cb(GtkAction *action, gpointer data)
10068 Compose *compose = (Compose *)data;
10070 #ifndef USE_NEW_ADDRBOOK
10071 addressbook_open(compose);
10073 GError* error = NULL;
10074 addressbook_connect_signals(compose);
10075 addressbook_dbus_open(TRUE, &error);
10077 g_warning("%s", error->message);
10078 g_error_free(error);
10083 static void about_show_cb(GtkAction *action, gpointer data)
10088 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10090 Compose *compose = (Compose *)data;
10095 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10096 cm_return_if_fail(tmpl != NULL);
10098 msg = g_strdup_printf(_("Do you want to apply the template '%s'?"),
10100 val = alertpanel(_("Apply template"), msg,
10101 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10104 if (val == G_ALERTDEFAULT)
10105 compose_template_apply(compose, tmpl, TRUE);
10106 else if (val == G_ALERTALTERNATE)
10107 compose_template_apply(compose, tmpl, FALSE);
10110 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10112 Compose *compose = (Compose *)data;
10114 compose_exec_ext_editor(compose);
10117 static void compose_undo_cb(GtkAction *action, gpointer data)
10119 Compose *compose = (Compose *)data;
10120 gboolean prev_autowrap = compose->autowrap;
10122 compose->autowrap = FALSE;
10123 undo_undo(compose->undostruct);
10124 compose->autowrap = prev_autowrap;
10127 static void compose_redo_cb(GtkAction *action, gpointer data)
10129 Compose *compose = (Compose *)data;
10130 gboolean prev_autowrap = compose->autowrap;
10132 compose->autowrap = FALSE;
10133 undo_redo(compose->undostruct);
10134 compose->autowrap = prev_autowrap;
10137 static void entry_cut_clipboard(GtkWidget *entry)
10139 if (GTK_IS_EDITABLE(entry))
10140 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10141 else if (GTK_IS_TEXT_VIEW(entry))
10142 gtk_text_buffer_cut_clipboard(
10143 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10144 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10148 static void entry_copy_clipboard(GtkWidget *entry)
10150 if (GTK_IS_EDITABLE(entry))
10151 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10152 else if (GTK_IS_TEXT_VIEW(entry))
10153 gtk_text_buffer_copy_clipboard(
10154 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10155 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10158 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10159 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10161 if (GTK_IS_TEXT_VIEW(entry)) {
10162 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10163 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10164 GtkTextIter start_iter, end_iter;
10166 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10168 if (contents == NULL)
10171 /* we shouldn't delete the selection when middle-click-pasting, or we
10172 * can't mid-click-paste our own selection */
10173 if (clip != GDK_SELECTION_PRIMARY) {
10174 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10175 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10178 if (insert_place == NULL) {
10179 /* if insert_place isn't specified, insert at the cursor.
10180 * used for Ctrl-V pasting */
10181 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10182 start = gtk_text_iter_get_offset(&start_iter);
10183 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10185 /* if insert_place is specified, paste here.
10186 * used for mid-click-pasting */
10187 start = gtk_text_iter_get_offset(insert_place);
10188 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10189 if (prefs_common.primary_paste_unselects)
10190 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10194 /* paste unwrapped: mark the paste so it's not wrapped later */
10195 end = start + strlen(contents);
10196 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10197 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10198 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10199 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10200 /* rewrap paragraph now (after a mid-click-paste) */
10201 mark_start = gtk_text_buffer_get_insert(buffer);
10202 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10203 gtk_text_iter_backward_char(&start_iter);
10204 compose_beautify_paragraph(compose, &start_iter, TRUE);
10206 } else if (GTK_IS_EDITABLE(entry))
10207 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10209 compose->modified = TRUE;
10212 static void entry_allsel(GtkWidget *entry)
10214 if (GTK_IS_EDITABLE(entry))
10215 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10216 else if (GTK_IS_TEXT_VIEW(entry)) {
10217 GtkTextIter startiter, enditer;
10218 GtkTextBuffer *textbuf;
10220 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10221 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10222 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10224 gtk_text_buffer_move_mark_by_name(textbuf,
10225 "selection_bound", &startiter);
10226 gtk_text_buffer_move_mark_by_name(textbuf,
10227 "insert", &enditer);
10231 static void compose_cut_cb(GtkAction *action, gpointer data)
10233 Compose *compose = (Compose *)data;
10234 if (compose->focused_editable
10235 #ifndef GENERIC_UMPC
10236 && gtk_widget_has_focus(compose->focused_editable)
10239 entry_cut_clipboard(compose->focused_editable);
10242 static void compose_copy_cb(GtkAction *action, gpointer data)
10244 Compose *compose = (Compose *)data;
10245 if (compose->focused_editable
10246 #ifndef GENERIC_UMPC
10247 && gtk_widget_has_focus(compose->focused_editable)
10250 entry_copy_clipboard(compose->focused_editable);
10253 static void compose_paste_cb(GtkAction *action, gpointer data)
10255 Compose *compose = (Compose *)data;
10256 gint prev_autowrap;
10257 GtkTextBuffer *buffer;
10259 if (compose->focused_editable &&
10260 #ifndef GENERIC_UMPC
10261 gtk_widget_has_focus(compose->focused_editable)
10264 entry_paste_clipboard(compose, compose->focused_editable,
10265 prefs_common.linewrap_pastes,
10266 GDK_SELECTION_CLIPBOARD, NULL);
10271 #ifndef GENERIC_UMPC
10272 gtk_widget_has_focus(compose->text) &&
10274 compose->gtkaspell &&
10275 compose->gtkaspell->check_while_typing)
10276 gtkaspell_highlight_all(compose->gtkaspell);
10280 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10282 Compose *compose = (Compose *)data;
10283 gint wrap_quote = prefs_common.linewrap_quote;
10284 if (compose->focused_editable
10285 #ifndef GENERIC_UMPC
10286 && gtk_widget_has_focus(compose->focused_editable)
10289 /* let text_insert() (called directly or at a later time
10290 * after the gtk_editable_paste_clipboard) know that
10291 * text is to be inserted as a quotation. implemented
10292 * by using a simple refcount... */
10293 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10294 G_OBJECT(compose->focused_editable),
10295 "paste_as_quotation"));
10296 g_object_set_data(G_OBJECT(compose->focused_editable),
10297 "paste_as_quotation",
10298 GINT_TO_POINTER(paste_as_quotation + 1));
10299 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10300 entry_paste_clipboard(compose, compose->focused_editable,
10301 prefs_common.linewrap_pastes,
10302 GDK_SELECTION_CLIPBOARD, NULL);
10303 prefs_common.linewrap_quote = wrap_quote;
10307 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10309 Compose *compose = (Compose *)data;
10310 gint prev_autowrap;
10311 GtkTextBuffer *buffer;
10313 if (compose->focused_editable
10314 #ifndef GENERIC_UMPC
10315 && gtk_widget_has_focus(compose->focused_editable)
10318 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10319 GDK_SELECTION_CLIPBOARD, NULL);
10324 #ifndef GENERIC_UMPC
10325 gtk_widget_has_focus(compose->text) &&
10327 compose->gtkaspell &&
10328 compose->gtkaspell->check_while_typing)
10329 gtkaspell_highlight_all(compose->gtkaspell);
10333 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10335 Compose *compose = (Compose *)data;
10336 gint prev_autowrap;
10337 GtkTextBuffer *buffer;
10339 if (compose->focused_editable
10340 #ifndef GENERIC_UMPC
10341 && gtk_widget_has_focus(compose->focused_editable)
10344 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10345 GDK_SELECTION_CLIPBOARD, NULL);
10350 #ifndef GENERIC_UMPC
10351 gtk_widget_has_focus(compose->text) &&
10353 compose->gtkaspell &&
10354 compose->gtkaspell->check_while_typing)
10355 gtkaspell_highlight_all(compose->gtkaspell);
10359 static void compose_allsel_cb(GtkAction *action, gpointer data)
10361 Compose *compose = (Compose *)data;
10362 if (compose->focused_editable
10363 #ifndef GENERIC_UMPC
10364 && gtk_widget_has_focus(compose->focused_editable)
10367 entry_allsel(compose->focused_editable);
10370 static void textview_move_beginning_of_line (GtkTextView *text)
10372 GtkTextBuffer *buffer;
10376 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10378 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10379 mark = gtk_text_buffer_get_insert(buffer);
10380 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10381 gtk_text_iter_set_line_offset(&ins, 0);
10382 gtk_text_buffer_place_cursor(buffer, &ins);
10385 static void textview_move_forward_character (GtkTextView *text)
10387 GtkTextBuffer *buffer;
10391 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10393 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10394 mark = gtk_text_buffer_get_insert(buffer);
10395 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10396 if (gtk_text_iter_forward_cursor_position(&ins))
10397 gtk_text_buffer_place_cursor(buffer, &ins);
10400 static void textview_move_backward_character (GtkTextView *text)
10402 GtkTextBuffer *buffer;
10406 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10408 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10409 mark = gtk_text_buffer_get_insert(buffer);
10410 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10411 if (gtk_text_iter_backward_cursor_position(&ins))
10412 gtk_text_buffer_place_cursor(buffer, &ins);
10415 static void textview_move_forward_word (GtkTextView *text)
10417 GtkTextBuffer *buffer;
10422 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10424 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10425 mark = gtk_text_buffer_get_insert(buffer);
10426 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10427 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10428 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10429 gtk_text_iter_backward_word_start(&ins);
10430 gtk_text_buffer_place_cursor(buffer, &ins);
10434 static void textview_move_backward_word (GtkTextView *text)
10436 GtkTextBuffer *buffer;
10440 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10442 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10443 mark = gtk_text_buffer_get_insert(buffer);
10444 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10445 if (gtk_text_iter_backward_word_starts(&ins, 1))
10446 gtk_text_buffer_place_cursor(buffer, &ins);
10449 static void textview_move_end_of_line (GtkTextView *text)
10451 GtkTextBuffer *buffer;
10455 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10457 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10458 mark = gtk_text_buffer_get_insert(buffer);
10459 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10460 if (gtk_text_iter_forward_to_line_end(&ins))
10461 gtk_text_buffer_place_cursor(buffer, &ins);
10464 static void textview_move_next_line (GtkTextView *text)
10466 GtkTextBuffer *buffer;
10471 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10473 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10474 mark = gtk_text_buffer_get_insert(buffer);
10475 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10476 offset = gtk_text_iter_get_line_offset(&ins);
10477 if (gtk_text_iter_forward_line(&ins)) {
10478 gtk_text_iter_set_line_offset(&ins, offset);
10479 gtk_text_buffer_place_cursor(buffer, &ins);
10483 static void textview_move_previous_line (GtkTextView *text)
10485 GtkTextBuffer *buffer;
10490 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10492 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10493 mark = gtk_text_buffer_get_insert(buffer);
10494 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10495 offset = gtk_text_iter_get_line_offset(&ins);
10496 if (gtk_text_iter_backward_line(&ins)) {
10497 gtk_text_iter_set_line_offset(&ins, offset);
10498 gtk_text_buffer_place_cursor(buffer, &ins);
10502 static void textview_delete_forward_character (GtkTextView *text)
10504 GtkTextBuffer *buffer;
10506 GtkTextIter ins, end_iter;
10508 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10510 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10511 mark = gtk_text_buffer_get_insert(buffer);
10512 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10514 if (gtk_text_iter_forward_char(&end_iter)) {
10515 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10519 static void textview_delete_backward_character (GtkTextView *text)
10521 GtkTextBuffer *buffer;
10523 GtkTextIter ins, end_iter;
10525 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10527 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10528 mark = gtk_text_buffer_get_insert(buffer);
10529 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10531 if (gtk_text_iter_backward_char(&end_iter)) {
10532 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10536 static void textview_delete_forward_word (GtkTextView *text)
10538 GtkTextBuffer *buffer;
10540 GtkTextIter ins, end_iter;
10542 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10544 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10545 mark = gtk_text_buffer_get_insert(buffer);
10546 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10548 if (gtk_text_iter_forward_word_end(&end_iter)) {
10549 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10553 static void textview_delete_backward_word (GtkTextView *text)
10555 GtkTextBuffer *buffer;
10557 GtkTextIter ins, end_iter;
10559 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10561 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10562 mark = gtk_text_buffer_get_insert(buffer);
10563 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10565 if (gtk_text_iter_backward_word_start(&end_iter)) {
10566 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10570 static void textview_delete_line (GtkTextView *text)
10572 GtkTextBuffer *buffer;
10574 GtkTextIter ins, start_iter, end_iter;
10576 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10578 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10579 mark = gtk_text_buffer_get_insert(buffer);
10580 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10583 gtk_text_iter_set_line_offset(&start_iter, 0);
10586 if (gtk_text_iter_ends_line(&end_iter)){
10587 if (!gtk_text_iter_forward_char(&end_iter))
10588 gtk_text_iter_backward_char(&start_iter);
10591 gtk_text_iter_forward_to_line_end(&end_iter);
10592 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10595 static void textview_delete_to_line_end (GtkTextView *text)
10597 GtkTextBuffer *buffer;
10599 GtkTextIter ins, end_iter;
10601 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10603 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10604 mark = gtk_text_buffer_get_insert(buffer);
10605 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10607 if (gtk_text_iter_ends_line(&end_iter))
10608 gtk_text_iter_forward_char(&end_iter);
10610 gtk_text_iter_forward_to_line_end(&end_iter);
10611 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10614 #define DO_ACTION(name, act) { \
10615 if(!strcmp(name, a_name)) { \
10619 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10621 const gchar *a_name = gtk_action_get_name(action);
10622 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10623 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10624 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10625 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10626 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10627 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10628 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10629 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10630 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10631 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10632 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10633 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10634 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10635 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10639 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10641 Compose *compose = (Compose *)data;
10642 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10643 ComposeCallAdvancedAction action = -1;
10645 action = compose_call_advanced_action_from_path(gaction);
10648 void (*do_action) (GtkTextView *text);
10649 } action_table[] = {
10650 {textview_move_beginning_of_line},
10651 {textview_move_forward_character},
10652 {textview_move_backward_character},
10653 {textview_move_forward_word},
10654 {textview_move_backward_word},
10655 {textview_move_end_of_line},
10656 {textview_move_next_line},
10657 {textview_move_previous_line},
10658 {textview_delete_forward_character},
10659 {textview_delete_backward_character},
10660 {textview_delete_forward_word},
10661 {textview_delete_backward_word},
10662 {textview_delete_line},
10663 {textview_delete_to_line_end}
10666 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10668 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10669 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10670 if (action_table[action].do_action)
10671 action_table[action].do_action(text);
10673 g_warning("Not implemented yet.");
10677 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10679 GtkAllocation allocation;
10683 if (GTK_IS_EDITABLE(widget)) {
10684 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10685 gtk_editable_set_position(GTK_EDITABLE(widget),
10688 if ((parent = gtk_widget_get_parent(widget))
10689 && (parent = gtk_widget_get_parent(parent))
10690 && (parent = gtk_widget_get_parent(parent))) {
10691 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10692 gtk_widget_get_allocation(widget, &allocation);
10693 gint y = allocation.y;
10694 gint height = allocation.height;
10695 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10696 (GTK_SCROLLED_WINDOW(parent));
10698 gfloat value = gtk_adjustment_get_value(shown);
10699 gfloat upper = gtk_adjustment_get_upper(shown);
10700 gfloat page_size = gtk_adjustment_get_page_size(shown);
10701 if (y < (int)value) {
10702 gtk_adjustment_set_value(shown, y - 1);
10704 if ((y + height) > ((int)value + (int)page_size)) {
10705 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10706 gtk_adjustment_set_value(shown,
10707 y + height - (int)page_size - 1);
10709 gtk_adjustment_set_value(shown,
10710 (int)upper - (int)page_size - 1);
10717 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10718 compose->focused_editable = widget;
10720 #ifdef GENERIC_UMPC
10721 if (GTK_IS_TEXT_VIEW(widget)
10722 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10723 g_object_ref(compose->notebook);
10724 g_object_ref(compose->edit_vbox);
10725 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10726 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10727 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10728 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10729 g_object_unref(compose->notebook);
10730 g_object_unref(compose->edit_vbox);
10731 g_signal_handlers_block_by_func(G_OBJECT(widget),
10732 G_CALLBACK(compose_grab_focus_cb),
10734 gtk_widget_grab_focus(widget);
10735 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10736 G_CALLBACK(compose_grab_focus_cb),
10738 } else if (!GTK_IS_TEXT_VIEW(widget)
10739 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10740 g_object_ref(compose->notebook);
10741 g_object_ref(compose->edit_vbox);
10742 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10743 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10744 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10745 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10746 g_object_unref(compose->notebook);
10747 g_object_unref(compose->edit_vbox);
10748 g_signal_handlers_block_by_func(G_OBJECT(widget),
10749 G_CALLBACK(compose_grab_focus_cb),
10751 gtk_widget_grab_focus(widget);
10752 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10753 G_CALLBACK(compose_grab_focus_cb),
10759 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10761 compose->modified = TRUE;
10762 // compose_beautify_paragraph(compose, NULL, TRUE);
10763 #ifndef GENERIC_UMPC
10764 compose_set_title(compose);
10768 static void compose_wrap_cb(GtkAction *action, gpointer data)
10770 Compose *compose = (Compose *)data;
10771 compose_beautify_paragraph(compose, NULL, TRUE);
10774 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10776 Compose *compose = (Compose *)data;
10777 compose_wrap_all_full(compose, TRUE);
10780 static void compose_find_cb(GtkAction *action, gpointer data)
10782 Compose *compose = (Compose *)data;
10784 message_search_compose(compose);
10787 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10790 Compose *compose = (Compose *)data;
10791 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10792 if (compose->autowrap)
10793 compose_wrap_all_full(compose, TRUE);
10794 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10797 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10800 Compose *compose = (Compose *)data;
10801 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10804 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10806 Compose *compose = (Compose *)data;
10808 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10811 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10813 Compose *compose = (Compose *)data;
10815 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10818 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10820 g_free(compose->privacy_system);
10822 compose->privacy_system = g_strdup(account->default_privacy_system);
10823 compose_update_privacy_system_menu_item(compose, warn);
10826 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10828 Compose *compose = (Compose *)data;
10830 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10831 gtk_widget_show(compose->ruler_hbox);
10832 prefs_common.show_ruler = TRUE;
10834 gtk_widget_hide(compose->ruler_hbox);
10835 gtk_widget_queue_resize(compose->edit_vbox);
10836 prefs_common.show_ruler = FALSE;
10840 static void compose_attach_drag_received_cb (GtkWidget *widget,
10841 GdkDragContext *context,
10844 GtkSelectionData *data,
10847 gpointer user_data)
10849 Compose *compose = (Compose *)user_data;
10853 type = gtk_selection_data_get_data_type(data);
10854 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10856 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10858 ) && gtk_drag_get_source_widget(context) !=
10859 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10860 list = uri_list_extract_filenames(
10861 (const gchar *)gtk_selection_data_get_data(data));
10862 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10863 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10864 compose_attach_append
10865 (compose, (const gchar *)tmp->data,
10866 utf8_filename, NULL, NULL);
10867 g_free(utf8_filename);
10869 if (list) compose_changed_cb(NULL, compose);
10870 list_free_strings(list);
10872 } else if (gtk_drag_get_source_widget(context)
10873 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10874 /* comes from our summaryview */
10875 SummaryView * summaryview = NULL;
10876 GSList * list = NULL, *cur = NULL;
10878 if (mainwindow_get_mainwindow())
10879 summaryview = mainwindow_get_mainwindow()->summaryview;
10882 list = summary_get_selected_msg_list(summaryview);
10884 for (cur = list; cur; cur = cur->next) {
10885 MsgInfo *msginfo = (MsgInfo *)cur->data;
10886 gchar *file = NULL;
10888 file = procmsg_get_message_file_full(msginfo,
10891 compose_attach_append(compose, (const gchar *)file,
10892 (const gchar *)file, "message/rfc822", NULL);
10896 g_slist_free(list);
10900 static gboolean compose_drag_drop(GtkWidget *widget,
10901 GdkDragContext *drag_context,
10903 guint time, gpointer user_data)
10905 /* not handling this signal makes compose_insert_drag_received_cb
10910 static gboolean completion_set_focus_to_subject
10911 (GtkWidget *widget,
10912 GdkEventKey *event,
10915 cm_return_val_if_fail(compose != NULL, FALSE);
10917 /* make backtab move to subject field */
10918 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10919 gtk_widget_grab_focus(compose->subject_entry);
10925 static void compose_insert_drag_received_cb (GtkWidget *widget,
10926 GdkDragContext *drag_context,
10929 GtkSelectionData *data,
10932 gpointer user_data)
10934 Compose *compose = (Compose *)user_data;
10938 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10940 type = gtk_selection_data_get_data_type(data);
10942 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10944 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10946 AlertValue val = G_ALERTDEFAULT;
10947 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10949 list = uri_list_extract_filenames(ddata);
10950 if (list == NULL && strstr(ddata, "://")) {
10951 /* Assume a list of no files, and data has ://, is a remote link */
10952 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10953 gchar *tmpfile = get_tmp_file();
10954 str_write_to_file(tmpdata, tmpfile);
10956 compose_insert_file(compose, tmpfile);
10957 claws_unlink(tmpfile);
10959 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10960 compose_beautify_paragraph(compose, NULL, TRUE);
10963 switch (prefs_common.compose_dnd_mode) {
10964 case COMPOSE_DND_ASK:
10965 val = alertpanel_full(_("Insert or attach?"),
10966 _("Do you want to insert the contents of the file(s) "
10967 "into the message body, or attach it to the email?"),
10968 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10969 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10971 case COMPOSE_DND_INSERT:
10972 val = G_ALERTALTERNATE;
10974 case COMPOSE_DND_ATTACH:
10975 val = G_ALERTOTHER;
10978 /* unexpected case */
10979 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10982 if (val & G_ALERTDISABLE) {
10983 val &= ~G_ALERTDISABLE;
10984 /* remember what action to perform by default, only if we don't click Cancel */
10985 if (val == G_ALERTALTERNATE)
10986 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10987 else if (val == G_ALERTOTHER)
10988 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10991 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10992 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10993 list_free_strings(list);
10996 } else if (val == G_ALERTOTHER) {
10997 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10998 list_free_strings(list);
11003 for (tmp = list; tmp != NULL; tmp = tmp->next) {
11004 compose_insert_file(compose, (const gchar *)tmp->data);
11006 list_free_strings(list);
11008 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11013 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11016 static void compose_header_drag_received_cb (GtkWidget *widget,
11017 GdkDragContext *drag_context,
11020 GtkSelectionData *data,
11023 gpointer user_data)
11025 GtkEditable *entry = (GtkEditable *)user_data;
11026 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11028 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11031 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11032 gchar *decoded=g_new(gchar, strlen(email));
11035 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11036 gtk_editable_delete_text(entry, 0, -1);
11037 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11038 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11042 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11045 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11047 Compose *compose = (Compose *)data;
11049 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11050 compose->return_receipt = TRUE;
11052 compose->return_receipt = FALSE;
11055 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11057 Compose *compose = (Compose *)data;
11059 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11060 compose->remove_references = TRUE;
11062 compose->remove_references = FALSE;
11065 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11066 ComposeHeaderEntry *headerentry)
11068 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11072 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11073 GdkEventKey *event,
11074 ComposeHeaderEntry *headerentry)
11076 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11077 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11078 !(event->state & GDK_MODIFIER_MASK) &&
11079 (event->keyval == GDK_KEY_BackSpace) &&
11080 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11081 gtk_container_remove
11082 (GTK_CONTAINER(headerentry->compose->header_table),
11083 headerentry->combo);
11084 gtk_container_remove
11085 (GTK_CONTAINER(headerentry->compose->header_table),
11086 headerentry->entry);
11087 headerentry->compose->header_list =
11088 g_slist_remove(headerentry->compose->header_list,
11090 g_free(headerentry);
11091 } else if (event->keyval == GDK_KEY_Tab) {
11092 if (headerentry->compose->header_last == headerentry) {
11093 /* Override default next focus, and give it to subject_entry
11094 * instead of notebook tabs
11096 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11097 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11104 static gboolean scroll_postpone(gpointer data)
11106 Compose *compose = (Compose *)data;
11108 cm_return_val_if_fail(!compose->batch, FALSE);
11110 GTK_EVENTS_FLUSH();
11111 compose_show_first_last_header(compose, FALSE);
11115 static void compose_headerentry_changed_cb(GtkWidget *entry,
11116 ComposeHeaderEntry *headerentry)
11118 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11119 compose_create_header_entry(headerentry->compose);
11120 g_signal_handlers_disconnect_matched
11121 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11122 0, 0, NULL, NULL, headerentry);
11124 if (!headerentry->compose->batch)
11125 g_timeout_add(0, scroll_postpone, headerentry->compose);
11129 static gboolean compose_defer_auto_save_draft(Compose *compose)
11131 compose->draft_timeout_tag = -1;
11132 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11136 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11138 GtkAdjustment *vadj;
11140 cm_return_if_fail(compose);
11141 cm_return_if_fail(!compose->batch);
11142 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11143 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11144 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11145 gtk_widget_get_parent(compose->header_table)));
11146 gtk_adjustment_set_value(vadj, (show_first ?
11147 gtk_adjustment_get_lower(vadj) :
11148 (gtk_adjustment_get_upper(vadj) -
11149 gtk_adjustment_get_page_size(vadj))));
11150 gtk_adjustment_changed(vadj);
11153 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11154 const gchar *text, gint len, Compose *compose)
11156 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11157 (G_OBJECT(compose->text), "paste_as_quotation"));
11160 cm_return_if_fail(text != NULL);
11162 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11163 G_CALLBACK(text_inserted),
11165 if (paste_as_quotation) {
11167 const gchar *qmark;
11169 GtkTextIter start_iter;
11172 len = strlen(text);
11174 new_text = g_strndup(text, len);
11176 qmark = compose_quote_char_from_context(compose);
11178 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11179 gtk_text_buffer_place_cursor(buffer, iter);
11181 pos = gtk_text_iter_get_offset(iter);
11183 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11184 _("Quote format error at line %d."));
11185 quote_fmt_reset_vartable();
11187 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11188 GINT_TO_POINTER(paste_as_quotation - 1));
11190 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11191 gtk_text_buffer_place_cursor(buffer, iter);
11192 gtk_text_buffer_delete_mark(buffer, mark);
11194 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11195 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11196 compose_beautify_paragraph(compose, &start_iter, FALSE);
11197 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11198 gtk_text_buffer_delete_mark(buffer, mark);
11200 if (strcmp(text, "\n") || compose->automatic_break
11201 || gtk_text_iter_starts_line(iter)) {
11202 GtkTextIter before_ins;
11203 gtk_text_buffer_insert(buffer, iter, text, len);
11204 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11205 before_ins = *iter;
11206 gtk_text_iter_backward_chars(&before_ins, len);
11207 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11210 /* check if the preceding is just whitespace or quote */
11211 GtkTextIter start_line;
11212 gchar *tmp = NULL, *quote = NULL;
11213 gint quote_len = 0, is_normal = 0;
11214 start_line = *iter;
11215 gtk_text_iter_set_line_offset(&start_line, 0);
11216 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11219 if (*tmp == '\0') {
11222 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11230 gtk_text_buffer_insert(buffer, iter, text, len);
11232 gtk_text_buffer_insert_with_tags_by_name(buffer,
11233 iter, text, len, "no_join", NULL);
11238 if (!paste_as_quotation) {
11239 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11240 compose_beautify_paragraph(compose, iter, FALSE);
11241 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11242 gtk_text_buffer_delete_mark(buffer, mark);
11245 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11246 G_CALLBACK(text_inserted),
11248 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11250 if (prefs_common.autosave &&
11251 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11252 compose->draft_timeout_tag != -2 /* disabled while loading */)
11253 compose->draft_timeout_tag = g_timeout_add
11254 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11258 static void compose_check_all(GtkAction *action, gpointer data)
11260 Compose *compose = (Compose *)data;
11261 if (!compose->gtkaspell)
11264 if (gtk_widget_has_focus(compose->subject_entry))
11265 claws_spell_entry_check_all(
11266 CLAWS_SPELL_ENTRY(compose->subject_entry));
11268 gtkaspell_check_all(compose->gtkaspell);
11271 static void compose_highlight_all(GtkAction *action, gpointer data)
11273 Compose *compose = (Compose *)data;
11274 if (compose->gtkaspell) {
11275 claws_spell_entry_recheck_all(
11276 CLAWS_SPELL_ENTRY(compose->subject_entry));
11277 gtkaspell_highlight_all(compose->gtkaspell);
11281 static void compose_check_backwards(GtkAction *action, gpointer data)
11283 Compose *compose = (Compose *)data;
11284 if (!compose->gtkaspell) {
11285 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11289 if (gtk_widget_has_focus(compose->subject_entry))
11290 claws_spell_entry_check_backwards(
11291 CLAWS_SPELL_ENTRY(compose->subject_entry));
11293 gtkaspell_check_backwards(compose->gtkaspell);
11296 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11298 Compose *compose = (Compose *)data;
11299 if (!compose->gtkaspell) {
11300 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11304 if (gtk_widget_has_focus(compose->subject_entry))
11305 claws_spell_entry_check_forwards_go(
11306 CLAWS_SPELL_ENTRY(compose->subject_entry));
11308 gtkaspell_check_forwards_go(compose->gtkaspell);
11313 *\brief Guess originating forward account from MsgInfo and several
11314 * "common preference" settings. Return NULL if no guess.
11316 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11318 PrefsAccount *account = NULL;
11320 cm_return_val_if_fail(msginfo, NULL);
11321 cm_return_val_if_fail(msginfo->folder, NULL);
11322 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11324 if (msginfo->folder->prefs->enable_default_account)
11325 account = account_find_from_id(msginfo->folder->prefs->default_account);
11328 account = msginfo->folder->folder->account;
11330 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11332 Xstrdup_a(to, msginfo->to, return NULL);
11333 extract_address(to);
11334 account = account_find_from_address(to, FALSE);
11337 if (!account && prefs_common.forward_account_autosel) {
11338 gchar cc[BUFFSIZE];
11339 if (!procheader_get_header_from_msginfo
11340 (msginfo, cc,sizeof cc , "Cc:")) {
11341 gchar *buf = cc + strlen("Cc:");
11342 extract_address(buf);
11343 account = account_find_from_address(buf, FALSE);
11347 if (!account && prefs_common.forward_account_autosel) {
11348 gchar deliveredto[BUFFSIZE];
11349 if (!procheader_get_header_from_msginfo
11350 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11351 gchar *buf = deliveredto + strlen("Delivered-To:");
11352 extract_address(buf);
11353 account = account_find_from_address(buf, FALSE);
11360 gboolean compose_close(Compose *compose)
11364 if (!g_mutex_trylock(compose->mutex)) {
11365 /* we have to wait for the (possibly deferred by auto-save)
11366 * drafting to be done, before destroying the compose under
11368 debug_print("waiting for drafting to finish...\n");
11369 compose_allow_user_actions(compose, FALSE);
11370 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11373 cm_return_val_if_fail(compose, FALSE);
11374 gtkut_widget_get_uposition(compose->window, &x, &y);
11375 if (!compose->batch) {
11376 prefs_common.compose_x = x;
11377 prefs_common.compose_y = y;
11379 g_mutex_unlock(compose->mutex);
11380 compose_destroy(compose);
11385 * Add entry field for each address in list.
11386 * \param compose E-Mail composition object.
11387 * \param listAddress List of (formatted) E-Mail addresses.
11389 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11392 node = listAddress;
11394 addr = ( gchar * ) node->data;
11395 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11396 node = g_list_next( node );
11400 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11401 guint action, gboolean opening_multiple)
11403 gchar *body = NULL;
11404 GSList *new_msglist = NULL;
11405 MsgInfo *tmp_msginfo = NULL;
11406 gboolean originally_enc = FALSE;
11407 gboolean originally_sig = FALSE;
11408 Compose *compose = NULL;
11409 gchar *s_system = NULL;
11411 cm_return_if_fail(msgview != NULL);
11413 cm_return_if_fail(msginfo_list != NULL);
11415 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11416 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11417 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11419 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11420 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11421 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11422 orig_msginfo, mimeinfo);
11423 if (tmp_msginfo != NULL) {
11424 new_msglist = g_slist_append(NULL, tmp_msginfo);
11426 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11427 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11428 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11430 tmp_msginfo->folder = orig_msginfo->folder;
11431 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11432 if (orig_msginfo->tags) {
11433 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11434 tmp_msginfo->folder->tags_dirty = TRUE;
11440 if (!opening_multiple)
11441 body = messageview_get_selection(msgview);
11444 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11445 procmsg_msginfo_free(tmp_msginfo);
11446 g_slist_free(new_msglist);
11448 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11450 if (compose && originally_enc) {
11451 compose_force_encryption(compose, compose->account, FALSE, s_system);
11454 if (compose && originally_sig && compose->account->default_sign_reply) {
11455 compose_force_signing(compose, compose->account, s_system);
11459 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11462 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11465 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11466 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11467 GSList *cur = msginfo_list;
11468 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11469 "messages. Opening the windows "
11470 "could take some time. Do you "
11471 "want to continue?"),
11472 g_slist_length(msginfo_list));
11473 if (g_slist_length(msginfo_list) > 9
11474 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11475 != G_ALERTALTERNATE) {
11480 /* We'll open multiple compose windows */
11481 /* let the WM place the next windows */
11482 compose_force_window_origin = FALSE;
11483 for (; cur; cur = cur->next) {
11485 tmplist.data = cur->data;
11486 tmplist.next = NULL;
11487 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11489 compose_force_window_origin = TRUE;
11491 /* forwarding multiple mails as attachments is done via a
11492 * single compose window */
11493 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11497 void compose_check_for_email_account(Compose *compose)
11499 PrefsAccount *ac = NULL, *curr = NULL;
11505 if (compose->account && compose->account->protocol == A_NNTP) {
11506 ac = account_get_cur_account();
11507 if (ac->protocol == A_NNTP) {
11508 list = account_get_list();
11510 for( ; list != NULL ; list = g_list_next(list)) {
11511 curr = (PrefsAccount *) list->data;
11512 if (curr->protocol != A_NNTP) {
11518 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11523 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11524 const gchar *address)
11526 GSList *msginfo_list = NULL;
11527 gchar *body = messageview_get_selection(msgview);
11530 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11532 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11533 compose_check_for_email_account(compose);
11534 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11535 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11536 compose_reply_set_subject(compose, msginfo);
11539 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11542 void compose_set_position(Compose *compose, gint pos)
11544 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11546 gtkut_text_view_set_position(text, pos);
11549 gboolean compose_search_string(Compose *compose,
11550 const gchar *str, gboolean case_sens)
11552 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11554 return gtkut_text_view_search_string(text, str, case_sens);
11557 gboolean compose_search_string_backward(Compose *compose,
11558 const gchar *str, gboolean case_sens)
11560 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11562 return gtkut_text_view_search_string_backward(text, str, case_sens);
11565 /* allocate a msginfo structure and populate its data from a compose data structure */
11566 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11568 MsgInfo *newmsginfo;
11570 gchar buf[BUFFSIZE];
11572 cm_return_val_if_fail( compose != NULL, NULL );
11574 newmsginfo = procmsg_msginfo_new();
11577 get_rfc822_date(buf, sizeof(buf));
11578 newmsginfo->date = g_strdup(buf);
11581 if (compose->from_name) {
11582 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11583 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11587 if (compose->subject_entry)
11588 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11590 /* to, cc, reply-to, newsgroups */
11591 for (list = compose->header_list; list; list = list->next) {
11592 gchar *header = gtk_editable_get_chars(
11594 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11595 gchar *entry = gtk_editable_get_chars(
11596 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11598 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11599 if ( newmsginfo->to == NULL ) {
11600 newmsginfo->to = g_strdup(entry);
11601 } else if (entry && *entry) {
11602 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11603 g_free(newmsginfo->to);
11604 newmsginfo->to = tmp;
11607 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11608 if ( newmsginfo->cc == NULL ) {
11609 newmsginfo->cc = g_strdup(entry);
11610 } else if (entry && *entry) {
11611 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11612 g_free(newmsginfo->cc);
11613 newmsginfo->cc = tmp;
11616 if ( strcasecmp(header,
11617 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11618 if ( newmsginfo->newsgroups == NULL ) {
11619 newmsginfo->newsgroups = g_strdup(entry);
11620 } else if (entry && *entry) {
11621 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11622 g_free(newmsginfo->newsgroups);
11623 newmsginfo->newsgroups = tmp;
11631 /* other data is unset */
11637 /* update compose's dictionaries from folder dict settings */
11638 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11639 FolderItem *folder_item)
11641 cm_return_if_fail(compose != NULL);
11643 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11644 FolderItemPrefs *prefs = folder_item->prefs;
11646 if (prefs->enable_default_dictionary)
11647 gtkaspell_change_dict(compose->gtkaspell,
11648 prefs->default_dictionary, FALSE);
11649 if (folder_item->prefs->enable_default_alt_dictionary)
11650 gtkaspell_change_alt_dict(compose->gtkaspell,
11651 prefs->default_alt_dictionary);
11652 if (prefs->enable_default_dictionary
11653 || prefs->enable_default_alt_dictionary)
11654 compose_spell_menu_changed(compose);
11659 static void compose_subject_entry_activated(GtkWidget *widget, gpointer data)
11661 Compose *compose = (Compose *)data;
11663 cm_return_if_fail(compose != NULL);
11665 gtk_widget_grab_focus(compose->text);