2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2009 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/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #include "addressbook.h"
64 #include "folderview.h"
67 #include "stock_pixmap.h"
68 #include "send_message.h"
71 #include "customheader.h"
72 #include "prefs_common.h"
73 #include "prefs_account.h"
77 #include "procheader.h"
79 #include "statusbar.h"
82 #include "quoted-printable.h"
87 #include "alertpanel.h"
88 #include "manage_window.h"
89 #include "gtkshruler.h"
91 #include "addr_compl.h"
92 #include "quote_fmt.h"
94 #include "foldersel.h"
97 #include "message_search.h"
102 #include "autofaces.h"
103 #include "spell_entry.h"
115 #define N_ATTACH_COLS (N_COL_COLUMNS)
119 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
120 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
121 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
122 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
123 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
124 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
127 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
133 } ComposeCallAdvancedAction;
137 PRIORITY_HIGHEST = 1,
146 COMPOSE_INSERT_SUCCESS,
147 COMPOSE_INSERT_READ_ERROR,
148 COMPOSE_INSERT_INVALID_CHARACTER,
149 COMPOSE_INSERT_NO_FILE
150 } ComposeInsertResult;
154 COMPOSE_WRITE_FOR_SEND,
155 COMPOSE_WRITE_FOR_STORE
160 COMPOSE_QUOTE_FORCED,
167 SUBJECT_FIELD_PRESENT,
172 #define B64_LINE_SIZE 57
173 #define B64_BUFFSIZE 77
175 #define MAX_REFERENCES_LEN 999
177 static GList *compose_list = NULL;
179 static Compose *compose_generic_new (PrefsAccount *account,
182 GPtrArray *attach_files,
183 GList *listAddress );
185 static Compose *compose_create (PrefsAccount *account,
190 static void compose_entry_mark_default_to (Compose *compose,
191 const gchar *address);
192 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
193 ComposeQuoteMode quote_mode,
197 static Compose *compose_forward_multiple (PrefsAccount *account,
198 GSList *msginfo_list);
199 static Compose *compose_reply (MsgInfo *msginfo,
200 ComposeQuoteMode quote_mode,
205 static Compose *compose_reply_mode (ComposeMode mode,
206 GSList *msginfo_list,
208 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
209 static void compose_update_privacy_systems_menu(Compose *compose);
211 static GtkWidget *compose_account_option_menu_create
213 static void compose_set_out_encoding (Compose *compose);
214 static void compose_set_template_menu (Compose *compose);
215 static void compose_destroy (Compose *compose);
217 static MailField compose_entries_set (Compose *compose,
219 ComposeEntryType to_type);
220 static gint compose_parse_header (Compose *compose,
222 static gchar *compose_parse_references (const gchar *ref,
225 static gchar *compose_quote_fmt (Compose *compose,
231 gboolean need_unescape,
232 const gchar *err_msg);
234 static void compose_reply_set_entry (Compose *compose,
240 followup_and_reply_to);
241 static void compose_reedit_set_entry (Compose *compose,
244 static void compose_insert_sig (Compose *compose,
246 static ComposeInsertResult compose_insert_file (Compose *compose,
249 static gboolean compose_attach_append (Compose *compose,
252 const gchar *content_type);
253 static void compose_attach_parts (Compose *compose,
256 static gboolean compose_beautify_paragraph (Compose *compose,
257 GtkTextIter *par_iter,
259 static void compose_wrap_all (Compose *compose);
260 static void compose_wrap_all_full (Compose *compose,
263 static void compose_set_title (Compose *compose);
264 static void compose_select_account (Compose *compose,
265 PrefsAccount *account,
268 static PrefsAccount *compose_current_mail_account(void);
269 /* static gint compose_send (Compose *compose); */
270 static gboolean compose_check_for_valid_recipient
272 static gboolean compose_check_entries (Compose *compose,
273 gboolean check_everything);
274 static gint compose_write_to_file (Compose *compose,
277 gboolean attach_parts);
278 static gint compose_write_body_to_file (Compose *compose,
280 static gint compose_remove_reedit_target (Compose *compose,
282 static void compose_remove_draft (Compose *compose);
283 static gint compose_queue_sub (Compose *compose,
287 gboolean check_subject,
288 gboolean remove_reedit_target);
289 static int compose_add_attachments (Compose *compose,
291 static gchar *compose_get_header (Compose *compose);
293 static void compose_convert_header (Compose *compose,
298 gboolean addr_field);
300 static void compose_attach_info_free (AttachInfo *ainfo);
301 static void compose_attach_remove_selected (GtkAction *action,
304 static void compose_template_apply (Compose *compose,
307 static void compose_attach_property (GtkAction *action,
309 static void compose_attach_property_create (gboolean *cancelled);
310 static void attach_property_ok (GtkWidget *widget,
311 gboolean *cancelled);
312 static void attach_property_cancel (GtkWidget *widget,
313 gboolean *cancelled);
314 static gint attach_property_delete_event (GtkWidget *widget,
316 gboolean *cancelled);
317 static gboolean attach_property_key_pressed (GtkWidget *widget,
319 gboolean *cancelled);
321 static void compose_exec_ext_editor (Compose *compose);
323 static gint compose_exec_ext_editor_real (const gchar *file);
324 static gboolean compose_ext_editor_kill (Compose *compose);
325 static gboolean compose_input_cb (GIOChannel *source,
326 GIOCondition condition,
328 static void compose_set_ext_editor_sensitive (Compose *compose,
330 #endif /* G_OS_UNIX */
332 static void compose_undo_state_changed (UndoMain *undostruct,
337 static void compose_create_header_entry (Compose *compose);
338 static void compose_add_header_entry (Compose *compose, const gchar *header,
339 gchar *text, ComposePrefType pref_type);
340 static void compose_remove_header_entries(Compose *compose);
342 static void compose_update_priority_menu_item(Compose * compose);
344 static void compose_spell_menu_changed (void *data);
345 static void compose_dict_changed (void *data);
347 static void compose_add_field_list ( Compose *compose,
348 GList *listAddress );
350 /* callback functions */
352 static gboolean compose_edit_size_alloc (GtkEditable *widget,
353 GtkAllocation *allocation,
354 GtkSHRuler *shruler);
355 static void account_activated (GtkComboBox *optmenu,
357 static void attach_selected (GtkTreeView *tree_view,
358 GtkTreePath *tree_path,
359 GtkTreeViewColumn *column,
361 static gboolean attach_button_pressed (GtkWidget *widget,
362 GdkEventButton *event,
364 static gboolean attach_key_pressed (GtkWidget *widget,
367 static void compose_send_cb (GtkAction *action, gpointer data);
368 static void compose_send_later_cb (GtkAction *action, gpointer data);
370 static void compose_save_cb (GtkAction *action,
373 static void compose_attach_cb (GtkAction *action,
375 static void compose_insert_file_cb (GtkAction *action,
377 static void compose_insert_sig_cb (GtkAction *action,
380 static void compose_close_cb (GtkAction *action,
383 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
385 static void compose_address_cb (GtkAction *action,
387 static void about_show_cb (GtkAction *action,
389 static void compose_template_activate_cb(GtkWidget *widget,
392 static void compose_ext_editor_cb (GtkAction *action,
395 static gint compose_delete_cb (GtkWidget *widget,
399 static void compose_undo_cb (GtkAction *action,
401 static void compose_redo_cb (GtkAction *action,
403 static void compose_cut_cb (GtkAction *action,
405 static void compose_copy_cb (GtkAction *action,
407 static void compose_paste_cb (GtkAction *action,
409 static void compose_paste_as_quote_cb (GtkAction *action,
411 static void compose_paste_no_wrap_cb (GtkAction *action,
413 static void compose_paste_wrap_cb (GtkAction *action,
415 static void compose_allsel_cb (GtkAction *action,
418 static void compose_advanced_action_cb (GtkAction *action,
421 static void compose_grab_focus_cb (GtkWidget *widget,
424 static void compose_changed_cb (GtkTextBuffer *textbuf,
427 static void compose_wrap_cb (GtkAction *action,
429 static void compose_wrap_all_cb (GtkAction *action,
431 static void compose_find_cb (GtkAction *action,
433 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
435 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
438 static void compose_toggle_ruler_cb (GtkToggleAction *action,
440 static void compose_toggle_sign_cb (GtkToggleAction *action,
442 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
444 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
445 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
446 static void activate_privacy_system (Compose *compose,
447 PrefsAccount *account,
449 static void compose_use_signing(Compose *compose, gboolean use_signing);
450 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
451 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
453 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
455 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
456 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
457 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
459 static void compose_attach_drag_received_cb (GtkWidget *widget,
460 GdkDragContext *drag_context,
463 GtkSelectionData *data,
467 static void compose_insert_drag_received_cb (GtkWidget *widget,
468 GdkDragContext *drag_context,
471 GtkSelectionData *data,
475 static void compose_header_drag_received_cb (GtkWidget *widget,
476 GdkDragContext *drag_context,
479 GtkSelectionData *data,
484 static gboolean compose_drag_drop (GtkWidget *widget,
485 GdkDragContext *drag_context,
487 guint time, gpointer user_data);
489 static void text_inserted (GtkTextBuffer *buffer,
494 static Compose *compose_generic_reply(MsgInfo *msginfo,
495 ComposeQuoteMode quote_mode,
499 gboolean followup_and_reply_to,
502 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
503 ComposeHeaderEntry *headerentry);
504 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
506 ComposeHeaderEntry *headerentry);
507 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
508 ComposeHeaderEntry *headerentry);
510 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
512 static void compose_allow_user_actions (Compose *compose, gboolean allow);
514 static void compose_nothing_cb (GtkAction *action, gpointer data)
520 static void compose_check_all (GtkAction *action, gpointer data);
521 static void compose_highlight_all (GtkAction *action, gpointer data);
522 static void compose_check_backwards (GtkAction *action, gpointer data);
523 static void compose_check_forwards_go (GtkAction *action, gpointer data);
526 static gint compose_defer_auto_save_draft (Compose *compose);
527 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
529 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
532 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
533 FolderItem *folder_item);
535 static void compose_attach_update_label(Compose *compose);
536 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
537 gboolean respect_default_to);
539 static GtkActionEntry compose_popup_entries[] =
541 {"Compose", NULL, "Compose" },
542 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
543 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
544 {"Compose/---", NULL, "---", NULL, NULL, NULL },
545 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
548 static GtkActionEntry compose_entries[] =
550 {"Menu", NULL, "Menu" },
552 {"Message", NULL, N_("_Message") },
553 {"Edit", NULL, N_("_Edit") },
555 {"Spelling", NULL, N_("_Spelling") },
557 {"Options", NULL, N_("_Options") },
558 {"Tools", NULL, N_("_Tools") },
559 {"Help", NULL, N_("_Help") },
561 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
562 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
563 {"Message/---", NULL, "---" },
565 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
566 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
567 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
568 /* {"Message/---", NULL, "---" }, */
569 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
570 /* {"Message/---", NULL, "---" }, */
571 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
574 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
575 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
576 {"Edit/---", NULL, "---" },
578 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
579 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
580 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
582 {"Edit/SpecialPaste", NULL, N_("Special paste") },
583 {"Edit/SpecialPaste/AsQuotation", NULL, N_("as _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
584 {"Edit/SpecialPaste/Wrapped", NULL, N_("_wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
585 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
587 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
589 {"Edit/Advanced", NULL, N_("A_dvanced") },
590 {"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*/
591 {"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*/
592 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
593 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
594 {"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*/
595 {"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*/
596 {"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*/
597 {"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*/
598 {"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*/
599 {"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*/
600 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
601 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
602 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
603 {"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*/
605 /* {"Edit/---", NULL, "---" }, */
606 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
608 /* {"Edit/---", NULL, "---" }, */
609 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
610 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
611 /* {"Edit/---", NULL, "---" }, */
612 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
615 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
616 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
617 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
618 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
620 {"Spelling/---", NULL, "---" },
621 {"Spelling/Options", NULL, N_("_Options") },
626 {"Options/ReplyMode", NULL, N_("Reply _mode") },
627 {"Options/---", NULL, "---" },
628 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
629 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
631 /* {"Options/---", NULL, "---" }, */
633 {"Options/Priority", NULL, N_("_Priority") },
635 {"Options/Encoding", NULL, N_("Character _encoding") },
636 {"Options/Encoding/---", NULL, "---" },
637 #define ENC_ACTION(cs_char,c_char,string) \
638 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
640 {"Options/Encoding/Western", NULL, N_("Western European") },
641 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
642 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
643 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
644 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
645 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
646 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
647 {"Options/Encoding/Korean", NULL, N_("Korean") },
648 {"Options/Encoding/Thai", NULL, N_("Thai") },
651 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
653 {"Tools/Template", NULL, N_("_Template") },
654 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
655 {"Tools/Actions", NULL, N_("Actio_ns") },
656 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
659 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
662 static GtkToggleActionEntry compose_toggle_entries[] =
664 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
665 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
666 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
667 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
668 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
669 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
670 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
673 static GtkRadioActionEntry compose_radio_rm_entries[] =
675 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
676 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
677 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
678 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
681 static GtkRadioActionEntry compose_radio_prio_entries[] =
683 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
684 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
685 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
686 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
687 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
690 static GtkRadioActionEntry compose_radio_enc_entries[] =
692 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
693 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
694 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
695 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
696 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
697 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
698 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
699 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
700 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
701 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
702 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
703 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
704 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
705 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
706 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
707 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
708 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
709 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
710 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
711 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
712 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
713 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
714 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
715 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
716 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
726 static GtkTargetEntry compose_mime_types[] =
728 {"text/uri-list", 0, 0},
729 {"UTF8_STRING", 0, 0},
733 static gboolean compose_put_existing_to_front(MsgInfo *info)
735 GList *compose_list = compose_get_compose_list();
739 for (elem = compose_list; elem != NULL && elem->data != NULL;
741 Compose *c = (Compose*)elem->data;
743 if (!c->targetinfo || !c->targetinfo->msgid ||
747 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
748 gtkut_window_popup(c->window);
756 static GdkColor quote_color1 =
757 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
758 static GdkColor quote_color2 =
759 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
760 static GdkColor quote_color3 =
761 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
763 static GdkColor quote_bgcolor1 =
764 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
765 static GdkColor quote_bgcolor2 =
766 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
767 static GdkColor quote_bgcolor3 =
768 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
770 static GdkColor signature_color = {
777 static GdkColor uri_color = {
784 static void compose_create_tags(GtkTextView *text, Compose *compose)
786 GtkTextBuffer *buffer;
787 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
793 buffer = gtk_text_view_get_buffer(text);
795 if (prefs_common.enable_color) {
796 /* grab the quote colors, converting from an int to a GdkColor */
797 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
799 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
801 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
803 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
805 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
807 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
809 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
811 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
814 signature_color = quote_color1 = quote_color2 = quote_color3 =
815 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
818 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
819 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
820 "foreground-gdk", "e_color1,
821 "paragraph-background-gdk", "e_bgcolor1,
823 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
824 "foreground-gdk", "e_color2,
825 "paragraph-background-gdk", "e_bgcolor2,
827 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
828 "foreground-gdk", "e_color3,
829 "paragraph-background-gdk", "e_bgcolor3,
832 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
833 "foreground-gdk", "e_color1,
835 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
836 "foreground-gdk", "e_color2,
838 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
839 "foreground-gdk", "e_color3,
843 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
844 "foreground-gdk", &signature_color,
847 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
848 "foreground-gdk", &uri_color,
850 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
851 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
853 color[0] = quote_color1;
854 color[1] = quote_color2;
855 color[2] = quote_color3;
856 color[3] = quote_bgcolor1;
857 color[4] = quote_bgcolor2;
858 color[5] = quote_bgcolor3;
859 color[6] = signature_color;
860 color[7] = uri_color;
861 cmap = gdk_drawable_get_colormap(compose->window->window);
862 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
864 for (i = 0; i < 8; i++) {
865 if (success[i] == FALSE) {
868 g_warning("Compose: color allocation failed.\n");
869 style = gtk_widget_get_style(GTK_WIDGET(text));
870 quote_color1 = quote_color2 = quote_color3 =
871 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
872 signature_color = uri_color = black;
877 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
878 GPtrArray *attach_files)
880 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
883 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
885 return compose_generic_new(account, mailto, item, NULL, NULL);
888 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
890 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
893 #define SCROLL_TO_CURSOR(compose) { \
894 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
895 gtk_text_view_get_buffer( \
896 GTK_TEXT_VIEW(compose->text))); \
897 gtk_text_view_scroll_mark_onscreen( \
898 GTK_TEXT_VIEW(compose->text), \
902 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
905 if (folderidentifier) {
906 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
907 prefs_common.compose_save_to_history = add_history(
908 prefs_common.compose_save_to_history, folderidentifier);
909 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
910 prefs_common.compose_save_to_history);
913 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
914 if (folderidentifier)
915 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
917 gtk_entry_set_text(GTK_ENTRY(entry), "");
920 static gchar *compose_get_save_to(Compose *compose)
923 gchar *result = NULL;
924 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
925 result = gtk_editable_get_chars(entry, 0, -1);
928 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
929 prefs_common.compose_save_to_history = add_history(
930 prefs_common.compose_save_to_history, result);
931 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
932 prefs_common.compose_save_to_history);
937 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
938 GPtrArray *attach_files, GList *listAddress )
941 GtkTextView *textview;
942 GtkTextBuffer *textbuf;
944 const gchar *subject_format = NULL;
945 const gchar *body_format = NULL;
946 gchar *mailto_from = NULL;
947 PrefsAccount *mailto_account = NULL;
948 MsgInfo* dummyinfo = NULL;
949 MailField mfield = NO_FIELD_PRESENT;
953 /* check if mailto defines a from */
954 if (mailto && *mailto != '\0') {
955 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
956 /* mailto defines a from, check if we can get account prefs from it,
957 if not, the account prefs will be guessed using other ways, but we'll keep
960 mailto_account = account_find_from_address(mailto_from, TRUE);
962 account = mailto_account;
965 /* if no account prefs set from mailto, set if from folder prefs (if any) */
966 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
967 account = account_find_from_id(item->prefs->default_account);
969 /* if no account prefs set, fallback to the current one */
970 if (!account) account = cur_account;
971 cm_return_val_if_fail(account != NULL, NULL);
973 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
975 /* override from name if mailto asked for it */
977 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
980 /* override from name according to folder properties */
981 if (item && item->prefs &&
982 item->prefs->compose_with_format &&
983 item->prefs->compose_override_from_format &&
984 *item->prefs->compose_override_from_format != '\0') {
989 dummyinfo = compose_msginfo_new_from_compose(compose);
991 /* decode \-escape sequences in the internal representation of the quote format */
992 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
993 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
996 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
999 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1001 quote_fmt_scan_string(tmp);
1004 buf = quote_fmt_get_buffer();
1006 alertpanel_error(_("New message From format error."));
1008 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1009 quote_fmt_reset_vartable();
1014 compose->replyinfo = NULL;
1015 compose->fwdinfo = NULL;
1017 textview = GTK_TEXT_VIEW(compose->text);
1018 textbuf = gtk_text_view_get_buffer(textview);
1019 compose_create_tags(textview, compose);
1021 undo_block(compose->undostruct);
1023 compose_set_dictionaries_from_folder_prefs(compose, item);
1026 if (account->auto_sig)
1027 compose_insert_sig(compose, FALSE);
1028 gtk_text_buffer_get_start_iter(textbuf, &iter);
1029 gtk_text_buffer_place_cursor(textbuf, &iter);
1031 if (account->protocol != A_NNTP) {
1032 if (mailto && *mailto != '\0') {
1033 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1036 compose_set_folder_prefs(compose, item, TRUE);
1038 if (item && item->ret_rcpt) {
1039 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1042 if (mailto && *mailto != '\0') {
1043 if (!strchr(mailto, '@'))
1044 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1046 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1047 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1048 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1049 mfield = TO_FIELD_PRESENT;
1052 * CLAWS: just don't allow return receipt request, even if the user
1053 * may want to send an email. simple but foolproof.
1055 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1057 compose_add_field_list( compose, listAddress );
1059 if (item && item->prefs && item->prefs->compose_with_format) {
1060 subject_format = item->prefs->compose_subject_format;
1061 body_format = item->prefs->compose_body_format;
1062 } else if (account->compose_with_format) {
1063 subject_format = account->compose_subject_format;
1064 body_format = account->compose_body_format;
1065 } else if (prefs_common.compose_with_format) {
1066 subject_format = prefs_common.compose_subject_format;
1067 body_format = prefs_common.compose_body_format;
1070 if (subject_format || body_format) {
1073 && *subject_format != '\0' )
1075 gchar *subject = NULL;
1080 dummyinfo = compose_msginfo_new_from_compose(compose);
1082 /* decode \-escape sequences in the internal representation of the quote format */
1083 tmp = malloc(strlen(subject_format)+1);
1084 pref_get_unescaped_pref(tmp, subject_format);
1086 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1088 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1089 compose->gtkaspell);
1091 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1093 quote_fmt_scan_string(tmp);
1096 buf = quote_fmt_get_buffer();
1098 alertpanel_error(_("New message subject format error."));
1100 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1101 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1102 quote_fmt_reset_vartable();
1106 mfield = SUBJECT_FIELD_PRESENT;
1110 && *body_format != '\0' )
1113 GtkTextBuffer *buffer;
1114 GtkTextIter start, end;
1118 dummyinfo = compose_msginfo_new_from_compose(compose);
1120 text = GTK_TEXT_VIEW(compose->text);
1121 buffer = gtk_text_view_get_buffer(text);
1122 gtk_text_buffer_get_start_iter(buffer, &start);
1123 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1124 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1126 compose_quote_fmt(compose, dummyinfo,
1128 NULL, tmp, FALSE, TRUE,
1129 _("The body of the \"New message\" template has an error at line %d."));
1130 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1131 quote_fmt_reset_vartable();
1135 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1136 gtkaspell_highlight_all(compose->gtkaspell);
1138 mfield = BODY_FIELD_PRESENT;
1142 procmsg_msginfo_free( dummyinfo );
1148 for (i = 0; i < attach_files->len; i++) {
1149 file = g_ptr_array_index(attach_files, i);
1150 compose_attach_append(compose, file, file, NULL);
1154 compose_show_first_last_header(compose, TRUE);
1156 /* Set save folder */
1157 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1158 gchar *folderidentifier;
1160 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1161 folderidentifier = folder_item_get_identifier(item);
1162 compose_set_save_to(compose, folderidentifier);
1163 g_free(folderidentifier);
1166 /* Place cursor according to provided input (mfield) */
1168 case NO_FIELD_PRESENT:
1169 if (compose->header_last)
1170 gtk_widget_grab_focus(compose->header_last->entry);
1172 case TO_FIELD_PRESENT:
1173 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1175 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1178 gtk_widget_grab_focus(compose->subject_entry);
1180 case SUBJECT_FIELD_PRESENT:
1181 textview = GTK_TEXT_VIEW(compose->text);
1184 textbuf = gtk_text_view_get_buffer(textview);
1187 mark = gtk_text_buffer_get_insert(textbuf);
1188 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1189 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1191 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1192 * only defers where it comes to the variable body
1193 * is not null. If no body is present compose->text
1194 * will be null in which case you cannot place the
1195 * cursor inside the component so. An empty component
1196 * is therefore created before placing the cursor
1198 case BODY_FIELD_PRESENT:
1199 gtk_widget_grab_focus(compose->text);
1203 undo_unblock(compose->undostruct);
1205 if (prefs_common.auto_exteditor)
1206 compose_exec_ext_editor(compose);
1208 compose->draft_timeout_tag = -1;
1209 SCROLL_TO_CURSOR(compose);
1211 compose->modified = FALSE;
1212 compose_set_title(compose);
1214 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1219 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1220 gboolean override_pref, const gchar *system)
1222 const gchar *privacy = NULL;
1224 cm_return_if_fail(compose != NULL);
1225 cm_return_if_fail(account != NULL);
1227 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1232 else if (account->default_privacy_system
1233 && strlen(account->default_privacy_system)) {
1234 privacy = account->default_privacy_system;
1236 GSList *privacy_avail = privacy_get_system_ids();
1237 if (privacy_avail && g_slist_length(privacy_avail)) {
1238 privacy = (gchar *)(privacy_avail->data);
1241 if (privacy != NULL) {
1243 g_free(compose->privacy_system);
1244 compose->privacy_system = NULL;
1246 if (compose->privacy_system == NULL)
1247 compose->privacy_system = g_strdup(privacy);
1248 else if (*(compose->privacy_system) == '\0') {
1249 g_free(compose->privacy_system);
1250 compose->privacy_system = g_strdup(privacy);
1252 compose_update_privacy_system_menu_item(compose, FALSE);
1253 compose_use_encryption(compose, TRUE);
1257 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1259 const gchar *privacy = NULL;
1263 else if (account->default_privacy_system
1264 && strlen(account->default_privacy_system)) {
1265 privacy = account->default_privacy_system;
1267 GSList *privacy_avail = privacy_get_system_ids();
1268 if (privacy_avail && g_slist_length(privacy_avail)) {
1269 privacy = (gchar *)(privacy_avail->data);
1273 if (privacy != NULL) {
1275 g_free(compose->privacy_system);
1276 compose->privacy_system = NULL;
1278 if (compose->privacy_system == NULL)
1279 compose->privacy_system = g_strdup(privacy);
1280 compose_update_privacy_system_menu_item(compose, FALSE);
1281 compose_use_signing(compose, TRUE);
1285 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1289 Compose *compose = NULL;
1291 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1293 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1294 cm_return_val_if_fail(msginfo != NULL, NULL);
1296 list_len = g_slist_length(msginfo_list);
1300 case COMPOSE_REPLY_TO_ADDRESS:
1301 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1302 FALSE, prefs_common.default_reply_list, FALSE, body);
1304 case COMPOSE_REPLY_WITH_QUOTE:
1305 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1306 FALSE, prefs_common.default_reply_list, FALSE, body);
1308 case COMPOSE_REPLY_WITHOUT_QUOTE:
1309 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1310 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1312 case COMPOSE_REPLY_TO_SENDER:
1313 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1314 FALSE, FALSE, TRUE, body);
1316 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1317 compose = compose_followup_and_reply_to(msginfo,
1318 COMPOSE_QUOTE_CHECK,
1319 FALSE, FALSE, body);
1321 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1322 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1323 FALSE, FALSE, TRUE, body);
1325 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1326 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1327 FALSE, FALSE, TRUE, NULL);
1329 case COMPOSE_REPLY_TO_ALL:
1330 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1331 TRUE, FALSE, FALSE, body);
1333 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1334 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1335 TRUE, FALSE, FALSE, body);
1337 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1338 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1339 TRUE, FALSE, FALSE, NULL);
1341 case COMPOSE_REPLY_TO_LIST:
1342 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1343 FALSE, TRUE, FALSE, body);
1345 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1346 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1347 FALSE, TRUE, FALSE, body);
1349 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1350 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1351 FALSE, TRUE, FALSE, NULL);
1353 case COMPOSE_FORWARD:
1354 if (prefs_common.forward_as_attachment) {
1355 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1358 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1362 case COMPOSE_FORWARD_INLINE:
1363 /* check if we reply to more than one Message */
1364 if (list_len == 1) {
1365 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1368 /* more messages FALL THROUGH */
1369 case COMPOSE_FORWARD_AS_ATTACH:
1370 compose = compose_forward_multiple(NULL, msginfo_list);
1372 case COMPOSE_REDIRECT:
1373 compose = compose_redirect(NULL, msginfo, FALSE);
1376 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1379 if (compose == NULL) {
1380 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1384 compose->rmode = mode;
1385 switch (compose->rmode) {
1387 case COMPOSE_REPLY_WITH_QUOTE:
1388 case COMPOSE_REPLY_WITHOUT_QUOTE:
1389 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1390 debug_print("reply mode Normal\n");
1391 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1392 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1394 case COMPOSE_REPLY_TO_SENDER:
1395 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1396 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1397 debug_print("reply mode Sender\n");
1398 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1400 case COMPOSE_REPLY_TO_ALL:
1401 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1402 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1403 debug_print("reply mode All\n");
1404 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1406 case COMPOSE_REPLY_TO_LIST:
1407 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1408 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1409 debug_print("reply mode List\n");
1410 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1412 case COMPOSE_REPLY_TO_ADDRESS:
1413 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1421 static Compose *compose_reply(MsgInfo *msginfo,
1422 ComposeQuoteMode quote_mode,
1428 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1429 to_sender, FALSE, body);
1432 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1433 ComposeQuoteMode quote_mode,
1438 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1439 to_sender, TRUE, body);
1442 static void compose_extract_original_charset(Compose *compose)
1444 MsgInfo *info = NULL;
1445 if (compose->replyinfo) {
1446 info = compose->replyinfo;
1447 } else if (compose->fwdinfo) {
1448 info = compose->fwdinfo;
1449 } else if (compose->targetinfo) {
1450 info = compose->targetinfo;
1453 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1454 MimeInfo *partinfo = mimeinfo;
1455 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1456 partinfo = procmime_mimeinfo_next(partinfo);
1458 compose->orig_charset =
1459 g_strdup(procmime_mimeinfo_get_parameter(
1460 partinfo, "charset"));
1462 procmime_mimeinfo_free_all(mimeinfo);
1466 #define SIGNAL_BLOCK(buffer) { \
1467 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1468 G_CALLBACK(compose_changed_cb), \
1470 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1471 G_CALLBACK(text_inserted), \
1475 #define SIGNAL_UNBLOCK(buffer) { \
1476 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1477 G_CALLBACK(compose_changed_cb), \
1479 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1480 G_CALLBACK(text_inserted), \
1484 static Compose *compose_generic_reply(MsgInfo *msginfo,
1485 ComposeQuoteMode quote_mode,
1486 gboolean to_all, gboolean to_ml,
1488 gboolean followup_and_reply_to,
1492 PrefsAccount *account = NULL;
1493 GtkTextView *textview;
1494 GtkTextBuffer *textbuf;
1495 gboolean quote = FALSE;
1496 const gchar *qmark = NULL;
1497 const gchar *body_fmt = NULL;
1498 gchar *s_system = NULL;
1500 cm_return_val_if_fail(msginfo != NULL, NULL);
1501 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1503 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1505 cm_return_val_if_fail(account != NULL, NULL);
1507 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1509 compose->updating = TRUE;
1511 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1512 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1514 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1515 if (!compose->replyinfo)
1516 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1518 compose_extract_original_charset(compose);
1520 if (msginfo->folder && msginfo->folder->ret_rcpt)
1521 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1523 /* Set save folder */
1524 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1525 gchar *folderidentifier;
1527 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1528 folderidentifier = folder_item_get_identifier(msginfo->folder);
1529 compose_set_save_to(compose, folderidentifier);
1530 g_free(folderidentifier);
1533 if (compose_parse_header(compose, msginfo) < 0) {
1534 compose->updating = FALSE;
1535 compose_destroy(compose);
1539 /* override from name according to folder properties */
1540 if (msginfo->folder && msginfo->folder->prefs &&
1541 msginfo->folder->prefs->reply_with_format &&
1542 msginfo->folder->prefs->reply_override_from_format &&
1543 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1548 /* decode \-escape sequences in the internal representation of the quote format */
1549 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1550 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1553 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1554 compose->gtkaspell);
1556 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1558 quote_fmt_scan_string(tmp);
1561 buf = quote_fmt_get_buffer();
1563 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1565 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1566 quote_fmt_reset_vartable();
1571 textview = (GTK_TEXT_VIEW(compose->text));
1572 textbuf = gtk_text_view_get_buffer(textview);
1573 compose_create_tags(textview, compose);
1575 undo_block(compose->undostruct);
1577 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1580 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1581 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1582 /* use the reply format of folder (if enabled), or the account's one
1583 (if enabled) or fallback to the global reply format, which is always
1584 enabled (even if empty), and use the relevant quotemark */
1586 if (msginfo->folder && msginfo->folder->prefs &&
1587 msginfo->folder->prefs->reply_with_format) {
1588 qmark = msginfo->folder->prefs->reply_quotemark;
1589 body_fmt = msginfo->folder->prefs->reply_body_format;
1591 } else if (account->reply_with_format) {
1592 qmark = account->reply_quotemark;
1593 body_fmt = account->reply_body_format;
1596 qmark = prefs_common.quotemark;
1597 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1598 body_fmt = gettext(prefs_common.quotefmt);
1605 /* empty quotemark is not allowed */
1606 if (qmark == NULL || *qmark == '\0')
1608 compose_quote_fmt(compose, compose->replyinfo,
1609 body_fmt, qmark, body, FALSE, TRUE,
1610 _("The body of the \"Reply\" template has an error at line %d."));
1611 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1612 quote_fmt_reset_vartable();
1614 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1615 gtkaspell_highlight_all(compose->gtkaspell);
1619 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1620 compose_force_encryption(compose, account, FALSE, s_system);
1623 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1624 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1625 compose_force_signing(compose, account, s_system);
1629 SIGNAL_BLOCK(textbuf);
1631 if (account->auto_sig)
1632 compose_insert_sig(compose, FALSE);
1634 compose_wrap_all(compose);
1636 SIGNAL_UNBLOCK(textbuf);
1638 gtk_widget_grab_focus(compose->text);
1640 undo_unblock(compose->undostruct);
1642 if (prefs_common.auto_exteditor)
1643 compose_exec_ext_editor(compose);
1645 compose->modified = FALSE;
1646 compose_set_title(compose);
1648 compose->updating = FALSE;
1649 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1650 SCROLL_TO_CURSOR(compose);
1652 if (compose->deferred_destroy) {
1653 compose_destroy(compose);
1661 #define INSERT_FW_HEADER(var, hdr) \
1662 if (msginfo->var && *msginfo->var) { \
1663 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1664 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1665 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1668 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1669 gboolean as_attach, const gchar *body,
1670 gboolean no_extedit,
1674 GtkTextView *textview;
1675 GtkTextBuffer *textbuf;
1678 cm_return_val_if_fail(msginfo != NULL, NULL);
1679 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1682 !(account = compose_guess_forward_account_from_msginfo
1684 account = cur_account;
1686 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1688 compose->updating = TRUE;
1689 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1690 if (!compose->fwdinfo)
1691 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1693 compose_extract_original_charset(compose);
1695 if (msginfo->subject && *msginfo->subject) {
1696 gchar *buf, *buf2, *p;
1698 buf = p = g_strdup(msginfo->subject);
1699 p += subject_get_prefix_length(p);
1700 memmove(buf, p, strlen(p) + 1);
1702 buf2 = g_strdup_printf("Fw: %s", buf);
1703 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1709 /* override from name according to folder properties */
1710 if (msginfo->folder && msginfo->folder->prefs &&
1711 msginfo->folder->prefs->forward_with_format &&
1712 msginfo->folder->prefs->forward_override_from_format &&
1713 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1717 MsgInfo *full_msginfo = NULL;
1720 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1722 full_msginfo = procmsg_msginfo_copy(msginfo);
1724 /* decode \-escape sequences in the internal representation of the quote format */
1725 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1726 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1729 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1730 compose->gtkaspell);
1732 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1734 quote_fmt_scan_string(tmp);
1737 buf = quote_fmt_get_buffer();
1739 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1741 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1742 quote_fmt_reset_vartable();
1745 procmsg_msginfo_free(full_msginfo);
1748 textview = GTK_TEXT_VIEW(compose->text);
1749 textbuf = gtk_text_view_get_buffer(textview);
1750 compose_create_tags(textview, compose);
1752 undo_block(compose->undostruct);
1756 msgfile = procmsg_get_message_file(msginfo);
1757 if (!is_file_exist(msgfile))
1758 g_warning("%s: file not exist\n", msgfile);
1760 compose_attach_append(compose, msgfile, msgfile,
1765 const gchar *qmark = NULL;
1766 const gchar *body_fmt = NULL;
1767 MsgInfo *full_msginfo;
1769 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1770 body_fmt = gettext(prefs_common.fw_quotefmt);
1774 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1776 full_msginfo = procmsg_msginfo_copy(msginfo);
1778 /* use the forward format of folder (if enabled), or the account's one
1779 (if enabled) or fallback to the global forward format, which is always
1780 enabled (even if empty), and use the relevant quotemark */
1781 if (msginfo->folder && msginfo->folder->prefs &&
1782 msginfo->folder->prefs->forward_with_format) {
1783 qmark = msginfo->folder->prefs->forward_quotemark;
1784 body_fmt = msginfo->folder->prefs->forward_body_format;
1786 } else if (account->forward_with_format) {
1787 qmark = account->forward_quotemark;
1788 body_fmt = account->forward_body_format;
1791 qmark = prefs_common.fw_quotemark;
1792 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1793 body_fmt = gettext(prefs_common.fw_quotefmt);
1798 /* empty quotemark is not allowed */
1799 if (qmark == NULL || *qmark == '\0')
1802 compose_quote_fmt(compose, full_msginfo,
1803 body_fmt, qmark, body, FALSE, TRUE,
1804 _("The body of the \"Forward\" template has an error at line %d."));
1805 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1806 quote_fmt_reset_vartable();
1807 compose_attach_parts(compose, msginfo);
1809 procmsg_msginfo_free(full_msginfo);
1811 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1812 gtkaspell_highlight_all(compose->gtkaspell);
1816 SIGNAL_BLOCK(textbuf);
1818 if (account->auto_sig)
1819 compose_insert_sig(compose, FALSE);
1821 compose_wrap_all(compose);
1823 SIGNAL_UNBLOCK(textbuf);
1825 gtk_text_buffer_get_start_iter(textbuf, &iter);
1826 gtk_text_buffer_place_cursor(textbuf, &iter);
1828 gtk_widget_grab_focus(compose->header_last->entry);
1830 if (!no_extedit && prefs_common.auto_exteditor)
1831 compose_exec_ext_editor(compose);
1834 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1835 gchar *folderidentifier;
1837 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1838 folderidentifier = folder_item_get_identifier(msginfo->folder);
1839 compose_set_save_to(compose, folderidentifier);
1840 g_free(folderidentifier);
1843 undo_unblock(compose->undostruct);
1845 compose->modified = FALSE;
1846 compose_set_title(compose);
1848 compose->updating = FALSE;
1849 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1850 SCROLL_TO_CURSOR(compose);
1852 if (compose->deferred_destroy) {
1853 compose_destroy(compose);
1857 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1862 #undef INSERT_FW_HEADER
1864 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1867 GtkTextView *textview;
1868 GtkTextBuffer *textbuf;
1872 gboolean single_mail = TRUE;
1874 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1876 if (g_slist_length(msginfo_list) > 1)
1877 single_mail = FALSE;
1879 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1880 if (((MsgInfo *)msginfo->data)->folder == NULL)
1883 /* guess account from first selected message */
1885 !(account = compose_guess_forward_account_from_msginfo
1886 (msginfo_list->data)))
1887 account = cur_account;
1889 cm_return_val_if_fail(account != NULL, NULL);
1891 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1892 if (msginfo->data) {
1893 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1894 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1898 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1899 g_warning("no msginfo_list");
1903 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1905 compose->updating = TRUE;
1907 /* override from name according to folder properties */
1908 if (msginfo_list->data) {
1909 MsgInfo *msginfo = msginfo_list->data;
1911 if (msginfo->folder && msginfo->folder->prefs &&
1912 msginfo->folder->prefs->forward_with_format &&
1913 msginfo->folder->prefs->forward_override_from_format &&
1914 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1919 /* decode \-escape sequences in the internal representation of the quote format */
1920 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1921 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1924 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1925 compose->gtkaspell);
1927 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1929 quote_fmt_scan_string(tmp);
1932 buf = quote_fmt_get_buffer();
1934 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1936 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1937 quote_fmt_reset_vartable();
1943 textview = GTK_TEXT_VIEW(compose->text);
1944 textbuf = gtk_text_view_get_buffer(textview);
1945 compose_create_tags(textview, compose);
1947 undo_block(compose->undostruct);
1948 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1949 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1951 if (!is_file_exist(msgfile))
1952 g_warning("%s: file not exist\n", msgfile);
1954 compose_attach_append(compose, msgfile, msgfile,
1960 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1961 if (info->subject && *info->subject) {
1962 gchar *buf, *buf2, *p;
1964 buf = p = g_strdup(info->subject);
1965 p += subject_get_prefix_length(p);
1966 memmove(buf, p, strlen(p) + 1);
1968 buf2 = g_strdup_printf("Fw: %s", buf);
1969 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1975 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1976 _("Fw: multiple emails"));
1979 SIGNAL_BLOCK(textbuf);
1981 if (account->auto_sig)
1982 compose_insert_sig(compose, FALSE);
1984 compose_wrap_all(compose);
1986 SIGNAL_UNBLOCK(textbuf);
1988 gtk_text_buffer_get_start_iter(textbuf, &iter);
1989 gtk_text_buffer_place_cursor(textbuf, &iter);
1991 gtk_widget_grab_focus(compose->header_last->entry);
1992 undo_unblock(compose->undostruct);
1993 compose->modified = FALSE;
1994 compose_set_title(compose);
1996 compose->updating = FALSE;
1997 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1998 SCROLL_TO_CURSOR(compose);
2000 if (compose->deferred_destroy) {
2001 compose_destroy(compose);
2005 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2010 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2012 GtkTextIter start = *iter;
2013 GtkTextIter end_iter;
2014 int start_pos = gtk_text_iter_get_offset(&start);
2016 if (!compose->account->sig_sep)
2019 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2020 start_pos+strlen(compose->account->sig_sep));
2022 /* check sig separator */
2023 str = gtk_text_iter_get_text(&start, &end_iter);
2024 if (!strcmp(str, compose->account->sig_sep)) {
2026 /* check end of line (\n) */
2027 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2028 start_pos+strlen(compose->account->sig_sep));
2029 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2030 start_pos+strlen(compose->account->sig_sep)+1);
2031 tmp = gtk_text_iter_get_text(&start, &end_iter);
2032 if (!strcmp(tmp,"\n")) {
2044 static void compose_colorize_signature(Compose *compose)
2046 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2048 GtkTextIter end_iter;
2049 gtk_text_buffer_get_start_iter(buffer, &iter);
2050 while (gtk_text_iter_forward_line(&iter))
2051 if (compose_is_sig_separator(compose, buffer, &iter)) {
2052 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2053 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2057 #define BLOCK_WRAP() { \
2058 prev_autowrap = compose->autowrap; \
2059 buffer = gtk_text_view_get_buffer( \
2060 GTK_TEXT_VIEW(compose->text)); \
2061 compose->autowrap = FALSE; \
2063 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2064 G_CALLBACK(compose_changed_cb), \
2066 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2067 G_CALLBACK(text_inserted), \
2070 #define UNBLOCK_WRAP() { \
2071 compose->autowrap = prev_autowrap; \
2072 if (compose->autowrap) { \
2073 gint old = compose->draft_timeout_tag; \
2074 compose->draft_timeout_tag = -2; \
2075 compose_wrap_all(compose); \
2076 compose->draft_timeout_tag = old; \
2079 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2080 G_CALLBACK(compose_changed_cb), \
2082 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2083 G_CALLBACK(text_inserted), \
2087 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2089 Compose *compose = NULL;
2090 PrefsAccount *account = NULL;
2091 GtkTextView *textview;
2092 GtkTextBuffer *textbuf;
2096 gchar buf[BUFFSIZE];
2097 gboolean use_signing = FALSE;
2098 gboolean use_encryption = FALSE;
2099 gchar *privacy_system = NULL;
2100 int priority = PRIORITY_NORMAL;
2101 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2102 gboolean autowrap = prefs_common.autowrap;
2103 gboolean autoindent = prefs_common.auto_indent;
2105 cm_return_val_if_fail(msginfo != NULL, NULL);
2106 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2108 if (compose_put_existing_to_front(msginfo)) {
2112 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2113 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2114 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2115 gchar queueheader_buf[BUFFSIZE];
2118 /* Select Account from queue headers */
2119 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2120 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2121 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2122 account = account_find_from_id(id);
2124 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2125 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2126 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2127 account = account_find_from_id(id);
2129 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2130 sizeof(queueheader_buf), "NAID:")) {
2131 id = atoi(&queueheader_buf[strlen("NAID:")]);
2132 account = account_find_from_id(id);
2134 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2135 sizeof(queueheader_buf), "MAID:")) {
2136 id = atoi(&queueheader_buf[strlen("MAID:")]);
2137 account = account_find_from_id(id);
2139 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2140 sizeof(queueheader_buf), "S:")) {
2141 account = account_find_from_address(queueheader_buf, FALSE);
2143 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2144 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2145 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2146 use_signing = param;
2149 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2150 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2151 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2152 use_signing = param;
2155 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2156 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2157 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2158 use_encryption = param;
2160 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2161 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2162 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2163 use_encryption = param;
2165 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2166 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2167 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2170 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2171 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2172 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2175 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2176 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2177 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2179 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2180 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2181 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2183 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2184 sizeof(queueheader_buf), "X-Priority: ")) {
2185 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2188 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2189 sizeof(queueheader_buf), "RMID:")) {
2190 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2191 if (tokens[0] && tokens[1] && tokens[2]) {
2192 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2193 if (orig_item != NULL) {
2194 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2199 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2200 sizeof(queueheader_buf), "FMID:")) {
2201 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2202 if (tokens[0] && tokens[1] && tokens[2]) {
2203 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2204 if (orig_item != NULL) {
2205 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2211 account = msginfo->folder->folder->account;
2214 if (!account && prefs_common.reedit_account_autosel) {
2215 gchar from[BUFFSIZE];
2216 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2217 extract_address(from);
2218 account = account_find_from_address(from, FALSE);
2222 account = cur_account;
2224 cm_return_val_if_fail(account != NULL, NULL);
2226 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2228 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2229 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2230 compose->autowrap = autowrap;
2231 compose->replyinfo = replyinfo;
2232 compose->fwdinfo = fwdinfo;
2234 compose->updating = TRUE;
2235 compose->priority = priority;
2237 if (privacy_system != NULL) {
2238 compose->privacy_system = privacy_system;
2239 compose_use_signing(compose, use_signing);
2240 compose_use_encryption(compose, use_encryption);
2241 compose_update_privacy_system_menu_item(compose, FALSE);
2243 activate_privacy_system(compose, account, FALSE);
2246 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2248 compose_extract_original_charset(compose);
2250 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2251 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2252 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2253 gchar queueheader_buf[BUFFSIZE];
2255 /* Set message save folder */
2256 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2257 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2258 compose_set_save_to(compose, &queueheader_buf[4]);
2260 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2261 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2263 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2268 if (compose_parse_header(compose, msginfo) < 0) {
2269 compose->updating = FALSE;
2270 compose_destroy(compose);
2273 compose_reedit_set_entry(compose, msginfo);
2275 textview = GTK_TEXT_VIEW(compose->text);
2276 textbuf = gtk_text_view_get_buffer(textview);
2277 compose_create_tags(textview, compose);
2279 mark = gtk_text_buffer_get_insert(textbuf);
2280 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2282 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2283 G_CALLBACK(compose_changed_cb),
2286 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2287 fp = procmime_get_first_encrypted_text_content(msginfo);
2289 compose_force_encryption(compose, account, TRUE, NULL);
2292 fp = procmime_get_first_text_content(msginfo);
2295 g_warning("Can't get text part\n");
2299 gboolean prev_autowrap = compose->autowrap;
2300 GtkTextBuffer *buffer = textbuf;
2302 while (fgets(buf, sizeof(buf), fp) != NULL) {
2304 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2310 compose_attach_parts(compose, msginfo);
2312 compose_colorize_signature(compose);
2314 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2315 G_CALLBACK(compose_changed_cb),
2318 gtk_widget_grab_focus(compose->text);
2320 if (prefs_common.auto_exteditor) {
2321 compose_exec_ext_editor(compose);
2323 compose->modified = FALSE;
2324 compose_set_title(compose);
2326 compose->updating = FALSE;
2327 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2328 SCROLL_TO_CURSOR(compose);
2330 if (compose->deferred_destroy) {
2331 compose_destroy(compose);
2335 compose->sig_str = account_get_signature_str(compose->account);
2337 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2342 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2349 cm_return_val_if_fail(msginfo != NULL, NULL);
2352 account = account_get_reply_account(msginfo,
2353 prefs_common.reply_account_autosel);
2354 cm_return_val_if_fail(account != NULL, NULL);
2356 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2358 compose->updating = TRUE;
2360 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2361 compose->replyinfo = NULL;
2362 compose->fwdinfo = NULL;
2364 compose_show_first_last_header(compose, TRUE);
2366 gtk_widget_grab_focus(compose->header_last->entry);
2368 filename = procmsg_get_message_file(msginfo);
2370 if (filename == NULL) {
2371 compose->updating = FALSE;
2372 compose_destroy(compose);
2377 compose->redirect_filename = filename;
2379 /* Set save folder */
2380 item = msginfo->folder;
2381 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2382 gchar *folderidentifier;
2384 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2385 folderidentifier = folder_item_get_identifier(item);
2386 compose_set_save_to(compose, folderidentifier);
2387 g_free(folderidentifier);
2390 compose_attach_parts(compose, msginfo);
2392 if (msginfo->subject)
2393 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2395 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2397 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2398 _("The body of the \"Redirect\" template has an error at line %d."));
2399 quote_fmt_reset_vartable();
2400 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2402 compose_colorize_signature(compose);
2405 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2406 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2407 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2410 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2411 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2412 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2413 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2414 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2415 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2416 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2418 if (compose->toolbar->draft_btn)
2419 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2420 if (compose->toolbar->insert_btn)
2421 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2422 if (compose->toolbar->attach_btn)
2423 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2424 if (compose->toolbar->sig_btn)
2425 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2426 if (compose->toolbar->exteditor_btn)
2427 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2428 if (compose->toolbar->linewrap_current_btn)
2429 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2430 if (compose->toolbar->linewrap_all_btn)
2431 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2433 compose->modified = FALSE;
2434 compose_set_title(compose);
2435 compose->updating = FALSE;
2436 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2437 SCROLL_TO_CURSOR(compose);
2439 if (compose->deferred_destroy) {
2440 compose_destroy(compose);
2444 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2449 GList *compose_get_compose_list(void)
2451 return compose_list;
2454 void compose_entry_append(Compose *compose, const gchar *address,
2455 ComposeEntryType type, ComposePrefType pref_type)
2457 const gchar *header;
2459 gboolean in_quote = FALSE;
2460 if (!address || *address == '\0') return;
2467 header = N_("Bcc:");
2469 case COMPOSE_REPLYTO:
2470 header = N_("Reply-To:");
2472 case COMPOSE_NEWSGROUPS:
2473 header = N_("Newsgroups:");
2475 case COMPOSE_FOLLOWUPTO:
2476 header = N_( "Followup-To:");
2478 case COMPOSE_INREPLYTO:
2479 header = N_( "In-Reply-To:");
2486 header = prefs_common_translated_header_name(header);
2488 cur = begin = (gchar *)address;
2490 /* we separate the line by commas, but not if we're inside a quoted
2492 while (*cur != '\0') {
2494 in_quote = !in_quote;
2495 if (*cur == ',' && !in_quote) {
2496 gchar *tmp = g_strdup(begin);
2498 tmp[cur-begin]='\0';
2501 while (*tmp == ' ' || *tmp == '\t')
2503 compose_add_header_entry(compose, header, tmp, pref_type);
2510 gchar *tmp = g_strdup(begin);
2512 tmp[cur-begin]='\0';
2515 while (*tmp == ' ' || *tmp == '\t')
2517 compose_add_header_entry(compose, header, tmp, pref_type);
2522 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2524 static GdkColor yellow;
2525 static GdkColor black;
2526 static gboolean yellow_initialised = FALSE;
2530 if (!yellow_initialised) {
2531 gdk_color_parse("#f5f6be", &yellow);
2532 gdk_color_parse("#000000", &black);
2533 yellow_initialised = gdk_colormap_alloc_color(
2534 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2535 yellow_initialised &= gdk_colormap_alloc_color(
2536 gdk_colormap_get_system(), &black, FALSE, TRUE);
2539 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2540 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2541 if (gtk_entry_get_text(entry) &&
2542 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2543 if (yellow_initialised) {
2544 gtk_widget_modify_base(
2545 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2546 GTK_STATE_NORMAL, &yellow);
2547 gtk_widget_modify_text(
2548 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2549 GTK_STATE_NORMAL, &black);
2555 void compose_toolbar_cb(gint action, gpointer data)
2557 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2558 Compose *compose = (Compose*)toolbar_item->parent;
2560 cm_return_if_fail(compose != NULL);
2564 compose_send_cb(NULL, compose);
2567 compose_send_later_cb(NULL, compose);
2570 compose_draft(compose, COMPOSE_QUIT_EDITING);
2573 compose_insert_file_cb(NULL, compose);
2576 compose_attach_cb(NULL, compose);
2579 compose_insert_sig(compose, FALSE);
2582 compose_ext_editor_cb(NULL, compose);
2584 case A_LINEWRAP_CURRENT:
2585 compose_beautify_paragraph(compose, NULL, TRUE);
2587 case A_LINEWRAP_ALL:
2588 compose_wrap_all_full(compose, TRUE);
2591 compose_address_cb(NULL, compose);
2594 case A_CHECK_SPELLING:
2595 compose_check_all(NULL, compose);
2603 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2608 gchar *subject = NULL;
2612 gchar **attach = NULL;
2613 gchar *inreplyto = NULL;
2614 MailField mfield = NO_FIELD_PRESENT;
2616 /* get mailto parts but skip from */
2617 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2620 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2621 mfield = TO_FIELD_PRESENT;
2624 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2626 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2628 if (!g_utf8_validate (subject, -1, NULL)) {
2629 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2630 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2633 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2635 mfield = SUBJECT_FIELD_PRESENT;
2638 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2639 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2642 gboolean prev_autowrap = compose->autowrap;
2644 compose->autowrap = FALSE;
2646 mark = gtk_text_buffer_get_insert(buffer);
2647 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2649 if (!g_utf8_validate (body, -1, NULL)) {
2650 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2651 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2654 gtk_text_buffer_insert(buffer, &iter, body, -1);
2656 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2658 compose->autowrap = prev_autowrap;
2659 if (compose->autowrap)
2660 compose_wrap_all(compose);
2661 mfield = BODY_FIELD_PRESENT;
2665 gint i = 0, att = 0;
2666 gchar *warn_files = NULL;
2667 while (attach[i] != NULL) {
2668 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2669 if (utf8_filename) {
2670 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2671 gchar *tmp = g_strdup_printf("%s%s\n",
2672 warn_files?warn_files:"",
2678 g_free(utf8_filename);
2680 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2685 alertpanel_notice(ngettext(
2686 "The following file has been attached: \n%s",
2687 "The following files have been attached: \n%s", att), warn_files);
2692 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2705 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2707 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2708 {"Cc:", NULL, TRUE},
2709 {"References:", NULL, FALSE},
2710 {"Bcc:", NULL, TRUE},
2711 {"Newsgroups:", NULL, TRUE},
2712 {"Followup-To:", NULL, TRUE},
2713 {"List-Post:", NULL, FALSE},
2714 {"X-Priority:", NULL, FALSE},
2715 {NULL, NULL, FALSE}};
2731 cm_return_val_if_fail(msginfo != NULL, -1);
2733 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2734 procheader_get_header_fields(fp, hentry);
2737 if (hentry[H_REPLY_TO].body != NULL) {
2738 if (hentry[H_REPLY_TO].body[0] != '\0') {
2740 conv_unmime_header(hentry[H_REPLY_TO].body,
2743 g_free(hentry[H_REPLY_TO].body);
2744 hentry[H_REPLY_TO].body = NULL;
2746 if (hentry[H_CC].body != NULL) {
2747 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2748 g_free(hentry[H_CC].body);
2749 hentry[H_CC].body = NULL;
2751 if (hentry[H_REFERENCES].body != NULL) {
2752 if (compose->mode == COMPOSE_REEDIT)
2753 compose->references = hentry[H_REFERENCES].body;
2755 compose->references = compose_parse_references
2756 (hentry[H_REFERENCES].body, msginfo->msgid);
2757 g_free(hentry[H_REFERENCES].body);
2759 hentry[H_REFERENCES].body = NULL;
2761 if (hentry[H_BCC].body != NULL) {
2762 if (compose->mode == COMPOSE_REEDIT)
2764 conv_unmime_header(hentry[H_BCC].body, NULL);
2765 g_free(hentry[H_BCC].body);
2766 hentry[H_BCC].body = NULL;
2768 if (hentry[H_NEWSGROUPS].body != NULL) {
2769 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2770 hentry[H_NEWSGROUPS].body = NULL;
2772 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2773 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2774 compose->followup_to =
2775 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2778 g_free(hentry[H_FOLLOWUP_TO].body);
2779 hentry[H_FOLLOWUP_TO].body = NULL;
2781 if (hentry[H_LIST_POST].body != NULL) {
2782 gchar *to = NULL, *start = NULL;
2784 extract_address(hentry[H_LIST_POST].body);
2785 if (hentry[H_LIST_POST].body[0] != '\0') {
2786 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2788 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2789 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2792 g_free(compose->ml_post);
2793 compose->ml_post = to;
2796 g_free(hentry[H_LIST_POST].body);
2797 hentry[H_LIST_POST].body = NULL;
2800 /* CLAWS - X-Priority */
2801 if (compose->mode == COMPOSE_REEDIT)
2802 if (hentry[H_X_PRIORITY].body != NULL) {
2805 priority = atoi(hentry[H_X_PRIORITY].body);
2806 g_free(hentry[H_X_PRIORITY].body);
2808 hentry[H_X_PRIORITY].body = NULL;
2810 if (priority < PRIORITY_HIGHEST ||
2811 priority > PRIORITY_LOWEST)
2812 priority = PRIORITY_NORMAL;
2814 compose->priority = priority;
2817 if (compose->mode == COMPOSE_REEDIT) {
2818 if (msginfo->inreplyto && *msginfo->inreplyto)
2819 compose->inreplyto = g_strdup(msginfo->inreplyto);
2823 if (msginfo->msgid && *msginfo->msgid)
2824 compose->inreplyto = g_strdup(msginfo->msgid);
2826 if (!compose->references) {
2827 if (msginfo->msgid && *msginfo->msgid) {
2828 if (msginfo->inreplyto && *msginfo->inreplyto)
2829 compose->references =
2830 g_strdup_printf("<%s>\n\t<%s>",
2834 compose->references =
2835 g_strconcat("<", msginfo->msgid, ">",
2837 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2838 compose->references =
2839 g_strconcat("<", msginfo->inreplyto, ">",
2847 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2849 GSList *ref_id_list, *cur;
2853 ref_id_list = references_list_append(NULL, ref);
2854 if (!ref_id_list) return NULL;
2855 if (msgid && *msgid)
2856 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2861 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2862 /* "<" + Message-ID + ">" + CR+LF+TAB */
2863 len += strlen((gchar *)cur->data) + 5;
2865 if (len > MAX_REFERENCES_LEN) {
2866 /* remove second message-ID */
2867 if (ref_id_list && ref_id_list->next &&
2868 ref_id_list->next->next) {
2869 g_free(ref_id_list->next->data);
2870 ref_id_list = g_slist_remove
2871 (ref_id_list, ref_id_list->next->data);
2873 slist_free_strings(ref_id_list);
2874 g_slist_free(ref_id_list);
2881 new_ref = g_string_new("");
2882 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2883 if (new_ref->len > 0)
2884 g_string_append(new_ref, "\n\t");
2885 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2888 slist_free_strings(ref_id_list);
2889 g_slist_free(ref_id_list);
2891 new_ref_str = new_ref->str;
2892 g_string_free(new_ref, FALSE);
2897 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2898 const gchar *fmt, const gchar *qmark,
2899 const gchar *body, gboolean rewrap,
2900 gboolean need_unescape,
2901 const gchar *err_msg)
2903 MsgInfo* dummyinfo = NULL;
2904 gchar *quote_str = NULL;
2906 gboolean prev_autowrap;
2907 const gchar *trimmed_body = body;
2908 gint cursor_pos = -1;
2909 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2910 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2915 SIGNAL_BLOCK(buffer);
2918 dummyinfo = compose_msginfo_new_from_compose(compose);
2919 msginfo = dummyinfo;
2922 if (qmark != NULL) {
2924 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2925 compose->gtkaspell);
2927 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2929 quote_fmt_scan_string(qmark);
2932 buf = quote_fmt_get_buffer();
2934 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2936 Xstrdup_a(quote_str, buf, goto error)
2939 if (fmt && *fmt != '\0') {
2942 while (*trimmed_body == '\n')
2946 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2947 compose->gtkaspell);
2949 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2951 if (need_unescape) {
2954 /* decode \-escape sequences in the internal representation of the quote format */
2955 tmp = malloc(strlen(fmt)+1);
2956 pref_get_unescaped_pref(tmp, fmt);
2957 quote_fmt_scan_string(tmp);
2961 quote_fmt_scan_string(fmt);
2965 buf = quote_fmt_get_buffer();
2967 gint line = quote_fmt_get_line();
2968 alertpanel_error(err_msg, line);
2974 prev_autowrap = compose->autowrap;
2975 compose->autowrap = FALSE;
2977 mark = gtk_text_buffer_get_insert(buffer);
2978 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2979 if (g_utf8_validate(buf, -1, NULL)) {
2980 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2982 gchar *tmpout = NULL;
2983 tmpout = conv_codeset_strdup
2984 (buf, conv_get_locale_charset_str_no_utf8(),
2986 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2988 tmpout = g_malloc(strlen(buf)*2+1);
2989 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2991 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2995 cursor_pos = quote_fmt_get_cursor_pos();
2996 if (cursor_pos == -1)
2997 cursor_pos = gtk_text_iter_get_offset(&iter);
2998 compose->set_cursor_pos = cursor_pos;
3000 gtk_text_buffer_get_start_iter(buffer, &iter);
3001 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3003 /* Some quotes may not end with new line - when replying with
3004 * quote from GtkTextBuffer selection for example. Add new
3005 * line character so cursor is at the beginning of the line */
3006 if (!gtk_text_iter_starts_line(&iter)) {
3007 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
3008 gtk_text_buffer_get_end_iter(buffer, &iter);
3009 compose->set_cursor_pos = gtk_text_iter_get_offset(&iter);
3011 gtk_text_buffer_place_cursor(buffer, &iter);
3013 compose->autowrap = prev_autowrap;
3014 if (compose->autowrap && rewrap)
3015 compose_wrap_all(compose);
3022 SIGNAL_UNBLOCK(buffer);
3024 procmsg_msginfo_free( dummyinfo );
3029 /* if ml_post is of type addr@host and from is of type
3030 * addr-anything@host, return TRUE
3032 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3034 gchar *left_ml = NULL;
3035 gchar *right_ml = NULL;
3036 gchar *left_from = NULL;
3037 gchar *right_from = NULL;
3038 gboolean result = FALSE;
3040 if (!ml_post || !from)
3043 left_ml = g_strdup(ml_post);
3044 if (strstr(left_ml, "@")) {
3045 right_ml = strstr(left_ml, "@")+1;
3046 *(strstr(left_ml, "@")) = '\0';
3049 left_from = g_strdup(from);
3050 if (strstr(left_from, "@")) {
3051 right_from = strstr(left_from, "@")+1;
3052 *(strstr(left_from, "@")) = '\0';
3055 if (left_ml && left_from && right_ml && right_from
3056 && !strncmp(left_from, left_ml, strlen(left_ml))
3057 && !strcmp(right_from, right_ml)) {
3066 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3067 gboolean respect_default_to)
3071 if (!folder || !folder->prefs)
3074 if (respect_default_to && folder->prefs->enable_default_to) {
3075 compose_entry_append(compose, folder->prefs->default_to,
3076 COMPOSE_TO, PREF_FOLDER);
3077 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3079 if (folder->prefs->enable_default_cc)
3080 compose_entry_append(compose, folder->prefs->default_cc,
3081 COMPOSE_CC, PREF_FOLDER);
3082 if (folder->prefs->enable_default_bcc)
3083 compose_entry_append(compose, folder->prefs->default_bcc,
3084 COMPOSE_BCC, PREF_FOLDER);
3085 if (folder->prefs->enable_default_replyto)
3086 compose_entry_append(compose, folder->prefs->default_replyto,
3087 COMPOSE_REPLYTO, PREF_FOLDER);
3090 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3095 if (!compose || !msginfo)
3098 if (msginfo->subject && *msginfo->subject) {
3099 buf = p = g_strdup(msginfo->subject);
3100 p += subject_get_prefix_length(p);
3101 memmove(buf, p, strlen(p) + 1);
3103 buf2 = g_strdup_printf("Re: %s", buf);
3104 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3109 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3112 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3113 gboolean to_all, gboolean to_ml,
3115 gboolean followup_and_reply_to)
3117 GSList *cc_list = NULL;
3120 gchar *replyto = NULL;
3121 gchar *ac_email = NULL;
3123 gboolean reply_to_ml = FALSE;
3124 gboolean default_reply_to = FALSE;
3126 cm_return_if_fail(compose->account != NULL);
3127 cm_return_if_fail(msginfo != NULL);
3129 reply_to_ml = to_ml && compose->ml_post;
3131 default_reply_to = msginfo->folder &&
3132 msginfo->folder->prefs->enable_default_reply_to;
3134 if (compose->account->protocol != A_NNTP) {
3135 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3137 if (reply_to_ml && !default_reply_to) {
3139 gboolean is_subscr = is_subscription(compose->ml_post,
3142 /* normal answer to ml post with a reply-to */
3143 compose_entry_append(compose,
3145 COMPOSE_TO, PREF_ML);
3146 if (compose->replyto)
3147 compose_entry_append(compose,
3149 COMPOSE_CC, PREF_ML);
3151 /* answer to subscription confirmation */
3152 if (compose->replyto)
3153 compose_entry_append(compose,
3155 COMPOSE_TO, PREF_ML);
3156 else if (msginfo->from)
3157 compose_entry_append(compose,
3159 COMPOSE_TO, PREF_ML);
3162 else if (!(to_all || to_sender) && default_reply_to) {
3163 compose_entry_append(compose,
3164 msginfo->folder->prefs->default_reply_to,
3165 COMPOSE_TO, PREF_FOLDER);
3166 compose_entry_mark_default_to(compose,
3167 msginfo->folder->prefs->default_reply_to);
3172 Xstrdup_a(tmp1, msginfo->from, return);
3173 extract_address(tmp1);
3174 if (to_all || to_sender ||
3175 !account_find_from_address(tmp1, FALSE))
3176 compose_entry_append(compose,
3177 (compose->replyto && !to_sender)
3178 ? compose->replyto :
3179 msginfo->from ? msginfo->from : "",
3180 COMPOSE_TO, PREF_NONE);
3181 else if (!to_all && !to_sender) {
3182 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3183 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3184 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3185 if (compose->replyto) {
3186 compose_entry_append(compose,
3188 COMPOSE_TO, PREF_NONE);
3190 compose_entry_append(compose,
3191 msginfo->from ? msginfo->from : "",
3192 COMPOSE_TO, PREF_NONE);
3195 /* replying to own mail, use original recp */
3196 compose_entry_append(compose,
3197 msginfo->to ? msginfo->to : "",
3198 COMPOSE_TO, PREF_NONE);
3199 compose_entry_append(compose,
3200 msginfo->cc ? msginfo->cc : "",
3201 COMPOSE_CC, PREF_NONE);
3206 if (to_sender || (compose->followup_to &&
3207 !strncmp(compose->followup_to, "poster", 6)))
3208 compose_entry_append
3210 (compose->replyto ? compose->replyto :
3211 msginfo->from ? msginfo->from : ""),
3212 COMPOSE_TO, PREF_NONE);
3214 else if (followup_and_reply_to || to_all) {
3215 compose_entry_append
3217 (compose->replyto ? compose->replyto :
3218 msginfo->from ? msginfo->from : ""),
3219 COMPOSE_TO, PREF_NONE);
3221 compose_entry_append
3223 compose->followup_to ? compose->followup_to :
3224 compose->newsgroups ? compose->newsgroups : "",
3225 COMPOSE_NEWSGROUPS, PREF_NONE);
3228 compose_entry_append
3230 compose->followup_to ? compose->followup_to :
3231 compose->newsgroups ? compose->newsgroups : "",
3232 COMPOSE_NEWSGROUPS, PREF_NONE);
3234 compose_reply_set_subject(compose, msginfo);
3236 if (to_ml && compose->ml_post) return;
3237 if (!to_all || compose->account->protocol == A_NNTP) return;
3239 if (compose->replyto) {
3240 Xstrdup_a(replyto, compose->replyto, return);
3241 extract_address(replyto);
3243 if (msginfo->from) {
3244 Xstrdup_a(from, msginfo->from, return);
3245 extract_address(from);
3248 if (replyto && from)
3249 cc_list = address_list_append_with_comments(cc_list, from);
3250 if (to_all && msginfo->folder &&
3251 msginfo->folder->prefs->enable_default_reply_to)
3252 cc_list = address_list_append_with_comments(cc_list,
3253 msginfo->folder->prefs->default_reply_to);
3254 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3255 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3257 ac_email = g_utf8_strdown(compose->account->address, -1);
3260 for (cur = cc_list; cur != NULL; cur = cur->next) {
3261 gchar *addr = g_utf8_strdown(cur->data, -1);
3262 extract_address(addr);
3264 if (strcmp(ac_email, addr))
3265 compose_entry_append(compose, (gchar *)cur->data,
3266 COMPOSE_CC, PREF_NONE);
3268 debug_print("Cc address same as compose account's, ignoring\n");
3273 slist_free_strings(cc_list);
3274 g_slist_free(cc_list);
3280 #define SET_ENTRY(entry, str) \
3283 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3286 #define SET_ADDRESS(type, str) \
3289 compose_entry_append(compose, str, type, PREF_NONE); \
3292 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3294 cm_return_if_fail(msginfo != NULL);
3296 SET_ENTRY(subject_entry, msginfo->subject);
3297 SET_ENTRY(from_name, msginfo->from);
3298 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3299 SET_ADDRESS(COMPOSE_CC, compose->cc);
3300 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3301 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3302 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3303 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3305 compose_update_priority_menu_item(compose);
3306 compose_update_privacy_system_menu_item(compose, FALSE);
3307 compose_show_first_last_header(compose, TRUE);
3313 static void compose_insert_sig(Compose *compose, gboolean replace)
3315 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3316 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3318 GtkTextIter iter, iter_end;
3319 gint cur_pos, ins_pos;
3320 gboolean prev_autowrap;
3321 gboolean found = FALSE;
3322 gboolean exists = FALSE;
3324 cm_return_if_fail(compose->account != NULL);
3328 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3329 G_CALLBACK(compose_changed_cb),
3332 mark = gtk_text_buffer_get_insert(buffer);
3333 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3334 cur_pos = gtk_text_iter_get_offset (&iter);
3337 gtk_text_buffer_get_end_iter(buffer, &iter);
3339 exists = (compose->sig_str != NULL);
3342 GtkTextIter first_iter, start_iter, end_iter;
3344 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3346 if (!exists || compose->sig_str[0] == '\0')
3349 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3350 compose->signature_tag);
3353 /* include previous \n\n */
3354 gtk_text_iter_backward_chars(&first_iter, 1);
3355 start_iter = first_iter;
3356 end_iter = first_iter;
3358 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3359 compose->signature_tag);
3360 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3361 compose->signature_tag);
3363 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3369 g_free(compose->sig_str);
3370 compose->sig_str = account_get_signature_str(compose->account);
3372 cur_pos = gtk_text_iter_get_offset(&iter);
3374 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3375 g_free(compose->sig_str);
3376 compose->sig_str = NULL;
3378 if (compose->sig_inserted == FALSE)
3379 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3380 compose->sig_inserted = TRUE;
3382 cur_pos = gtk_text_iter_get_offset(&iter);
3383 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3385 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3386 gtk_text_iter_forward_chars(&iter, 1);
3387 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3388 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3390 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3391 cur_pos = gtk_text_buffer_get_char_count (buffer);
3394 /* put the cursor where it should be
3395 * either where the quote_fmt says, either where it was */
3396 if (compose->set_cursor_pos < 0)
3397 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3399 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3400 compose->set_cursor_pos);
3402 compose->set_cursor_pos = -1;
3403 gtk_text_buffer_place_cursor(buffer, &iter);
3404 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3405 G_CALLBACK(compose_changed_cb),
3411 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3414 GtkTextBuffer *buffer;
3417 const gchar *cur_encoding;
3418 gchar buf[BUFFSIZE];
3421 gboolean prev_autowrap;
3422 gboolean badtxt = FALSE;
3423 struct stat file_stat;
3426 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3428 /* get the size of the file we are about to insert */
3429 ret = g_stat(file, &file_stat);
3431 gchar *shortfile = g_path_get_basename(file);
3432 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3434 return COMPOSE_INSERT_NO_FILE;
3435 } else if (prefs_common.warn_large_insert == TRUE) {
3437 /* ask user for confirmation if the file is large */
3438 if (prefs_common.warn_large_insert_size < 0 ||
3439 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3443 msg = g_strdup_printf(_("You are about to insert a file of %s "
3444 "in the message body. Are you sure you want to do that?"),
3445 to_human_readable(file_stat.st_size));
3446 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3447 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3450 /* do we ask for confirmation next time? */
3451 if (aval & G_ALERTDISABLE) {
3452 /* no confirmation next time, disable feature in preferences */
3453 aval &= ~G_ALERTDISABLE;
3454 prefs_common.warn_large_insert = FALSE;
3457 /* abort file insertion if user canceled action */
3458 if (aval != G_ALERTALTERNATE) {
3459 return COMPOSE_INSERT_NO_FILE;
3465 if ((fp = g_fopen(file, "rb")) == NULL) {
3466 FILE_OP_ERROR(file, "fopen");
3467 return COMPOSE_INSERT_READ_ERROR;
3470 prev_autowrap = compose->autowrap;
3471 compose->autowrap = FALSE;
3473 text = GTK_TEXT_VIEW(compose->text);
3474 buffer = gtk_text_view_get_buffer(text);
3475 mark = gtk_text_buffer_get_insert(buffer);
3476 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3478 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3479 G_CALLBACK(text_inserted),
3482 cur_encoding = conv_get_locale_charset_str_no_utf8();
3484 while (fgets(buf, sizeof(buf), fp) != NULL) {
3487 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3488 str = g_strdup(buf);
3490 str = conv_codeset_strdup
3491 (buf, cur_encoding, CS_INTERNAL);
3494 /* strip <CR> if DOS/Windows file,
3495 replace <CR> with <LF> if Macintosh file. */
3498 if (len > 0 && str[len - 1] != '\n') {
3500 if (str[len] == '\r') str[len] = '\n';
3503 gtk_text_buffer_insert(buffer, &iter, str, -1);
3507 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3508 G_CALLBACK(text_inserted),
3510 compose->autowrap = prev_autowrap;
3511 if (compose->autowrap)
3512 compose_wrap_all(compose);
3517 return COMPOSE_INSERT_INVALID_CHARACTER;
3519 return COMPOSE_INSERT_SUCCESS;
3522 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3523 const gchar *filename,
3524 const gchar *content_type)
3532 GtkListStore *store;
3534 gboolean has_binary = FALSE;
3536 if (!is_file_exist(file)) {
3537 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3538 gboolean result = FALSE;
3539 if (file_from_uri && is_file_exist(file_from_uri)) {
3540 result = compose_attach_append(
3541 compose, file_from_uri,
3545 g_free(file_from_uri);
3548 alertpanel_error("File %s doesn't exist\n", filename);
3551 if ((size = get_file_size(file)) < 0) {
3552 alertpanel_error("Can't get file size of %s\n", filename);
3556 alertpanel_error(_("File %s is empty."), filename);
3559 if ((fp = g_fopen(file, "rb")) == NULL) {
3560 alertpanel_error(_("Can't read %s."), filename);
3565 ainfo = g_new0(AttachInfo, 1);
3566 auto_ainfo = g_auto_pointer_new_with_free
3567 (ainfo, (GFreeFunc) compose_attach_info_free);
3568 ainfo->file = g_strdup(file);
3571 ainfo->content_type = g_strdup(content_type);
3572 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3574 MsgFlags flags = {0, 0};
3576 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3577 ainfo->encoding = ENC_7BIT;
3579 ainfo->encoding = ENC_8BIT;
3581 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3582 if (msginfo && msginfo->subject)
3583 name = g_strdup(msginfo->subject);
3585 name = g_path_get_basename(filename ? filename : file);
3587 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3589 procmsg_msginfo_free(msginfo);
3591 if (!g_ascii_strncasecmp(content_type, "text", 4))
3592 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3594 ainfo->encoding = ENC_BASE64;
3595 name = g_path_get_basename(filename ? filename : file);
3596 ainfo->name = g_strdup(name);
3600 ainfo->content_type = procmime_get_mime_type(file);
3601 if (!ainfo->content_type) {
3602 ainfo->content_type =
3603 g_strdup("application/octet-stream");
3604 ainfo->encoding = ENC_BASE64;
3605 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3607 procmime_get_encoding_for_text_file(file, &has_binary);
3609 ainfo->encoding = ENC_BASE64;
3610 name = g_path_get_basename(filename ? filename : file);
3611 ainfo->name = g_strdup(name);
3615 if (ainfo->name != NULL
3616 && !strcmp(ainfo->name, ".")) {
3617 g_free(ainfo->name);
3621 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3622 g_free(ainfo->content_type);
3623 ainfo->content_type = g_strdup("application/octet-stream");
3626 ainfo->size = (goffset)size;
3627 size_text = to_human_readable((goffset)size);
3629 store = GTK_LIST_STORE(gtk_tree_view_get_model
3630 (GTK_TREE_VIEW(compose->attach_clist)));
3632 gtk_list_store_append(store, &iter);
3633 gtk_list_store_set(store, &iter,
3634 COL_MIMETYPE, ainfo->content_type,
3635 COL_SIZE, size_text,
3636 COL_NAME, ainfo->name,
3638 COL_AUTODATA, auto_ainfo,
3641 g_auto_pointer_free(auto_ainfo);
3642 compose_attach_update_label(compose);
3646 static void compose_use_signing(Compose *compose, gboolean use_signing)
3648 compose->use_signing = use_signing;
3649 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3652 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3654 compose->use_encryption = use_encryption;
3655 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3658 #define NEXT_PART_NOT_CHILD(info) \
3660 node = info->node; \
3661 while (node->children) \
3662 node = g_node_last_child(node); \
3663 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3666 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3670 MimeInfo *firsttext = NULL;
3671 MimeInfo *encrypted = NULL;
3674 const gchar *partname = NULL;
3676 mimeinfo = procmime_scan_message(msginfo);
3677 if (!mimeinfo) return;
3679 if (mimeinfo->node->children == NULL) {
3680 procmime_mimeinfo_free_all(mimeinfo);
3684 /* find first content part */
3685 child = (MimeInfo *) mimeinfo->node->children->data;
3686 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3687 child = (MimeInfo *)child->node->children->data;
3690 if (child->type == MIMETYPE_TEXT) {
3692 debug_print("First text part found\n");
3693 } else if (compose->mode == COMPOSE_REEDIT &&
3694 child->type == MIMETYPE_APPLICATION &&
3695 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3696 encrypted = (MimeInfo *)child->node->parent->data;
3699 child = (MimeInfo *) mimeinfo->node->children->data;
3700 while (child != NULL) {
3703 if (child == encrypted) {
3704 /* skip this part of tree */
3705 NEXT_PART_NOT_CHILD(child);
3709 if (child->type == MIMETYPE_MULTIPART) {
3710 /* get the actual content */
3711 child = procmime_mimeinfo_next(child);
3715 if (child == firsttext) {
3716 child = procmime_mimeinfo_next(child);
3720 outfile = procmime_get_tmp_file_name(child);
3721 if ((err = procmime_get_part(outfile, child)) < 0)
3722 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3724 gchar *content_type;
3726 content_type = procmime_get_content_type_str(child->type, child->subtype);
3728 /* if we meet a pgp signature, we don't attach it, but
3729 * we force signing. */
3730 if ((strcmp(content_type, "application/pgp-signature") &&
3731 strcmp(content_type, "application/pkcs7-signature") &&
3732 strcmp(content_type, "application/x-pkcs7-signature"))
3733 || compose->mode == COMPOSE_REDIRECT) {
3734 partname = procmime_mimeinfo_get_parameter(child, "filename");
3735 if (partname == NULL)
3736 partname = procmime_mimeinfo_get_parameter(child, "name");
3737 if (partname == NULL)
3739 compose_attach_append(compose, outfile,
3740 partname, content_type);
3742 compose_force_signing(compose, compose->account, NULL);
3744 g_free(content_type);
3747 NEXT_PART_NOT_CHILD(child);
3749 procmime_mimeinfo_free_all(mimeinfo);
3752 #undef NEXT_PART_NOT_CHILD
3757 WAIT_FOR_INDENT_CHAR,
3758 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3761 /* return indent length, we allow:
3762 indent characters followed by indent characters or spaces/tabs,
3763 alphabets and numbers immediately followed by indent characters,
3764 and the repeating sequences of the above
3765 If quote ends with multiple spaces, only the first one is included. */
3766 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3767 const GtkTextIter *start, gint *len)
3769 GtkTextIter iter = *start;
3773 IndentState state = WAIT_FOR_INDENT_CHAR;
3776 gint alnum_count = 0;
3777 gint space_count = 0;
3780 if (prefs_common.quote_chars == NULL) {
3784 while (!gtk_text_iter_ends_line(&iter)) {
3785 wc = gtk_text_iter_get_char(&iter);
3786 if (g_unichar_iswide(wc))
3788 clen = g_unichar_to_utf8(wc, ch);
3792 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3793 is_space = g_unichar_isspace(wc);
3795 if (state == WAIT_FOR_INDENT_CHAR) {
3796 if (!is_indent && !g_unichar_isalnum(wc))
3799 quote_len += alnum_count + space_count + 1;
3800 alnum_count = space_count = 0;
3801 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3804 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3805 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3809 else if (is_indent) {
3810 quote_len += alnum_count + space_count + 1;
3811 alnum_count = space_count = 0;
3814 state = WAIT_FOR_INDENT_CHAR;
3818 gtk_text_iter_forward_char(&iter);
3821 if (quote_len > 0 && space_count > 0)
3827 if (quote_len > 0) {
3829 gtk_text_iter_forward_chars(&iter, quote_len);
3830 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3836 /* return >0 if the line is itemized */
3837 static int compose_itemized_length(GtkTextBuffer *buffer,
3838 const GtkTextIter *start)
3840 GtkTextIter iter = *start;
3845 if (gtk_text_iter_ends_line(&iter))
3850 wc = gtk_text_iter_get_char(&iter);
3851 if (!g_unichar_isspace(wc))
3853 gtk_text_iter_forward_char(&iter);
3854 if (gtk_text_iter_ends_line(&iter))
3858 clen = g_unichar_to_utf8(wc, ch);
3862 if (!strchr("*-+", ch[0]))
3865 gtk_text_iter_forward_char(&iter);
3866 if (gtk_text_iter_ends_line(&iter))
3868 wc = gtk_text_iter_get_char(&iter);
3869 if (g_unichar_isspace(wc)) {
3875 /* return the string at the start of the itemization */
3876 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3877 const GtkTextIter *start)
3879 GtkTextIter iter = *start;
3882 GString *item_chars = g_string_new("");
3885 if (gtk_text_iter_ends_line(&iter))
3890 wc = gtk_text_iter_get_char(&iter);
3891 if (!g_unichar_isspace(wc))
3893 gtk_text_iter_forward_char(&iter);
3894 if (gtk_text_iter_ends_line(&iter))
3896 g_string_append_unichar(item_chars, wc);
3899 str = item_chars->str;
3900 g_string_free(item_chars, FALSE);
3904 /* return the number of spaces at a line's start */
3905 static int compose_left_offset_length(GtkTextBuffer *buffer,
3906 const GtkTextIter *start)
3908 GtkTextIter iter = *start;
3911 if (gtk_text_iter_ends_line(&iter))
3915 wc = gtk_text_iter_get_char(&iter);
3916 if (!g_unichar_isspace(wc))
3919 gtk_text_iter_forward_char(&iter);
3920 if (gtk_text_iter_ends_line(&iter))
3924 gtk_text_iter_forward_char(&iter);
3925 if (gtk_text_iter_ends_line(&iter))
3930 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3931 const GtkTextIter *start,
3932 GtkTextIter *break_pos,
3936 GtkTextIter iter = *start, line_end = *start;
3937 PangoLogAttr *attrs;
3944 gboolean can_break = FALSE;
3945 gboolean do_break = FALSE;
3946 gboolean was_white = FALSE;
3947 gboolean prev_dont_break = FALSE;
3949 gtk_text_iter_forward_to_line_end(&line_end);
3950 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3951 len = g_utf8_strlen(str, -1);
3955 g_warning("compose_get_line_break_pos: len = 0!\n");
3959 /* g_print("breaking line: %d: %s (len = %d)\n",
3960 gtk_text_iter_get_line(&iter), str, len); */
3962 attrs = g_new(PangoLogAttr, len + 1);
3964 pango_default_break(str, -1, NULL, attrs, len + 1);
3968 /* skip quote and leading spaces */
3969 for (i = 0; *p != '\0' && i < len; i++) {
3972 wc = g_utf8_get_char(p);
3973 if (i >= quote_len && !g_unichar_isspace(wc))
3975 if (g_unichar_iswide(wc))
3977 else if (*p == '\t')
3981 p = g_utf8_next_char(p);
3984 for (; *p != '\0' && i < len; i++) {
3985 PangoLogAttr *attr = attrs + i;
3989 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3992 was_white = attr->is_white;
3994 /* don't wrap URI */
3995 if ((uri_len = get_uri_len(p)) > 0) {
3997 if (pos > 0 && col > max_col) {
4007 wc = g_utf8_get_char(p);
4008 if (g_unichar_iswide(wc)) {
4010 if (prev_dont_break && can_break && attr->is_line_break)
4012 } else if (*p == '\t')
4016 if (pos > 0 && col > max_col) {
4021 if (*p == '-' || *p == '/')
4022 prev_dont_break = TRUE;
4024 prev_dont_break = FALSE;
4026 p = g_utf8_next_char(p);
4030 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4035 *break_pos = *start;
4036 gtk_text_iter_set_line_offset(break_pos, pos);
4041 static gboolean compose_join_next_line(Compose *compose,
4042 GtkTextBuffer *buffer,
4044 const gchar *quote_str)
4046 GtkTextIter iter_ = *iter, cur, prev, next, end;
4047 PangoLogAttr attrs[3];
4049 gchar *next_quote_str;
4052 gboolean keep_cursor = FALSE;
4054 if (!gtk_text_iter_forward_line(&iter_) ||
4055 gtk_text_iter_ends_line(&iter_)) {
4058 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4060 if ((quote_str || next_quote_str) &&
4061 strcmp2(quote_str, next_quote_str) != 0) {
4062 g_free(next_quote_str);
4065 g_free(next_quote_str);
4068 if (quote_len > 0) {
4069 gtk_text_iter_forward_chars(&end, quote_len);
4070 if (gtk_text_iter_ends_line(&end)) {
4075 /* don't join itemized lines */
4076 if (compose_itemized_length(buffer, &end) > 0) {
4080 /* don't join signature separator */
4081 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4084 /* delete quote str */
4086 gtk_text_buffer_delete(buffer, &iter_, &end);
4088 /* don't join line breaks put by the user */
4090 gtk_text_iter_backward_char(&cur);
4091 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4092 gtk_text_iter_forward_char(&cur);
4096 gtk_text_iter_forward_char(&cur);
4097 /* delete linebreak and extra spaces */
4098 while (gtk_text_iter_backward_char(&cur)) {
4099 wc1 = gtk_text_iter_get_char(&cur);
4100 if (!g_unichar_isspace(wc1))
4105 while (!gtk_text_iter_ends_line(&cur)) {
4106 wc1 = gtk_text_iter_get_char(&cur);
4107 if (!g_unichar_isspace(wc1))
4109 gtk_text_iter_forward_char(&cur);
4112 if (!gtk_text_iter_equal(&prev, &next)) {
4115 mark = gtk_text_buffer_get_insert(buffer);
4116 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4117 if (gtk_text_iter_equal(&prev, &cur))
4119 gtk_text_buffer_delete(buffer, &prev, &next);
4123 /* insert space if required */
4124 gtk_text_iter_backward_char(&prev);
4125 wc1 = gtk_text_iter_get_char(&prev);
4126 wc2 = gtk_text_iter_get_char(&next);
4127 gtk_text_iter_forward_char(&next);
4128 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4129 pango_default_break(str, -1, NULL, attrs, 3);
4130 if (!attrs[1].is_line_break ||
4131 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4132 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4134 gtk_text_iter_backward_char(&iter_);
4135 gtk_text_buffer_place_cursor(buffer, &iter_);
4144 #define ADD_TXT_POS(bp_, ep_, pti_) \
4145 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4146 last = last->next; \
4147 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4148 last->next = NULL; \
4150 g_warning("alloc error scanning URIs\n"); \
4153 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4155 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4156 GtkTextBuffer *buffer;
4157 GtkTextIter iter, break_pos, end_of_line;
4158 gchar *quote_str = NULL;
4160 gboolean wrap_quote = prefs_common.linewrap_quote;
4161 gboolean prev_autowrap = compose->autowrap;
4162 gint startq_offset = -1, noq_offset = -1;
4163 gint uri_start = -1, uri_stop = -1;
4164 gint nouri_start = -1, nouri_stop = -1;
4165 gint num_blocks = 0;
4166 gint quotelevel = -1;
4167 gboolean modified = force;
4168 gboolean removed = FALSE;
4169 gboolean modified_before_remove = FALSE;
4171 gboolean start = TRUE;
4172 gint itemized_len = 0, rem_item_len = 0;
4173 gchar *itemized_chars = NULL;
4174 gboolean item_continuation = FALSE;
4179 if (compose->draft_timeout_tag == -2) {
4183 compose->autowrap = FALSE;
4185 buffer = gtk_text_view_get_buffer(text);
4186 undo_wrapping(compose->undostruct, TRUE);
4191 mark = gtk_text_buffer_get_insert(buffer);
4192 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4196 if (compose->draft_timeout_tag == -2) {
4197 if (gtk_text_iter_ends_line(&iter)) {
4198 while (gtk_text_iter_ends_line(&iter) &&
4199 gtk_text_iter_forward_line(&iter))
4202 while (gtk_text_iter_backward_line(&iter)) {
4203 if (gtk_text_iter_ends_line(&iter)) {
4204 gtk_text_iter_forward_line(&iter);
4210 /* move to line start */
4211 gtk_text_iter_set_line_offset(&iter, 0);
4214 itemized_len = compose_itemized_length(buffer, &iter);
4216 if (!itemized_len) {
4217 itemized_len = compose_left_offset_length(buffer, &iter);
4218 item_continuation = TRUE;
4222 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4224 /* go until paragraph end (empty line) */
4225 while (start || !gtk_text_iter_ends_line(&iter)) {
4226 gchar *scanpos = NULL;
4227 /* parse table - in order of priority */
4229 const gchar *needle; /* token */
4231 /* token search function */
4232 gchar *(*search) (const gchar *haystack,
4233 const gchar *needle);
4234 /* part parsing function */
4235 gboolean (*parse) (const gchar *start,
4236 const gchar *scanpos,
4240 /* part to URI function */
4241 gchar *(*build_uri) (const gchar *bp,
4245 static struct table parser[] = {
4246 {"http://", strcasestr, get_uri_part, make_uri_string},
4247 {"https://", strcasestr, get_uri_part, make_uri_string},
4248 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4249 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4250 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4251 {"www.", strcasestr, get_uri_part, make_http_string},
4252 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4253 {"@", strcasestr, get_email_part, make_email_string}
4255 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4256 gint last_index = PARSE_ELEMS;
4258 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4262 if (!prev_autowrap && num_blocks == 0) {
4264 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4265 G_CALLBACK(text_inserted),
4268 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4271 uri_start = uri_stop = -1;
4273 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4276 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4277 if (startq_offset == -1)
4278 startq_offset = gtk_text_iter_get_offset(&iter);
4279 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4280 if (quotelevel > 2) {
4281 /* recycle colors */
4282 if (prefs_common.recycle_quote_colors)
4291 if (startq_offset == -1)
4292 noq_offset = gtk_text_iter_get_offset(&iter);
4296 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4299 if (gtk_text_iter_ends_line(&iter)) {
4301 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4302 prefs_common.linewrap_len,
4304 GtkTextIter prev, next, cur;
4305 if (prev_autowrap != FALSE || force) {
4306 compose->automatic_break = TRUE;
4308 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4309 compose->automatic_break = FALSE;
4310 if (itemized_len && compose->autoindent) {
4311 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4312 if (!item_continuation)
4313 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4315 } else if (quote_str && wrap_quote) {
4316 compose->automatic_break = TRUE;
4318 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4319 compose->automatic_break = FALSE;
4320 if (itemized_len && compose->autoindent) {
4321 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4322 if (!item_continuation)
4323 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4327 /* remove trailing spaces */
4329 rem_item_len = itemized_len;
4330 while (compose->autoindent && rem_item_len-- > 0)
4331 gtk_text_iter_backward_char(&cur);
4332 gtk_text_iter_backward_char(&cur);
4335 while (!gtk_text_iter_starts_line(&cur)) {
4338 gtk_text_iter_backward_char(&cur);
4339 wc = gtk_text_iter_get_char(&cur);
4340 if (!g_unichar_isspace(wc))
4344 if (!gtk_text_iter_equal(&prev, &next)) {
4345 gtk_text_buffer_delete(buffer, &prev, &next);
4347 gtk_text_iter_forward_char(&break_pos);
4351 gtk_text_buffer_insert(buffer, &break_pos,
4355 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4357 /* move iter to current line start */
4358 gtk_text_iter_set_line_offset(&iter, 0);
4365 /* move iter to next line start */
4371 if (!prev_autowrap && num_blocks > 0) {
4373 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4374 G_CALLBACK(text_inserted),
4378 while (!gtk_text_iter_ends_line(&end_of_line)) {
4379 gtk_text_iter_forward_char(&end_of_line);
4381 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4383 nouri_start = gtk_text_iter_get_offset(&iter);
4384 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4386 walk_pos = gtk_text_iter_get_offset(&iter);
4387 /* FIXME: this looks phony. scanning for anything in the parse table */
4388 for (n = 0; n < PARSE_ELEMS; n++) {
4391 tmp = parser[n].search(walk, parser[n].needle);
4393 if (scanpos == NULL || tmp < scanpos) {
4402 /* check if URI can be parsed */
4403 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4404 (const gchar **)&ep, FALSE)
4405 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4409 strlen(parser[last_index].needle);
4412 uri_start = walk_pos + (bp - o_walk);
4413 uri_stop = walk_pos + (ep - o_walk);
4417 gtk_text_iter_forward_line(&iter);
4420 if (startq_offset != -1) {
4421 GtkTextIter startquote, endquote;
4422 gtk_text_buffer_get_iter_at_offset(
4423 buffer, &startquote, startq_offset);
4426 switch (quotelevel) {
4428 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4429 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4430 gtk_text_buffer_apply_tag_by_name(
4431 buffer, "quote0", &startquote, &endquote);
4432 gtk_text_buffer_remove_tag_by_name(
4433 buffer, "quote1", &startquote, &endquote);
4434 gtk_text_buffer_remove_tag_by_name(
4435 buffer, "quote2", &startquote, &endquote);
4440 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4441 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4442 gtk_text_buffer_apply_tag_by_name(
4443 buffer, "quote1", &startquote, &endquote);
4444 gtk_text_buffer_remove_tag_by_name(
4445 buffer, "quote0", &startquote, &endquote);
4446 gtk_text_buffer_remove_tag_by_name(
4447 buffer, "quote2", &startquote, &endquote);
4452 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4453 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4454 gtk_text_buffer_apply_tag_by_name(
4455 buffer, "quote2", &startquote, &endquote);
4456 gtk_text_buffer_remove_tag_by_name(
4457 buffer, "quote0", &startquote, &endquote);
4458 gtk_text_buffer_remove_tag_by_name(
4459 buffer, "quote1", &startquote, &endquote);
4465 } else if (noq_offset != -1) {
4466 GtkTextIter startnoquote, endnoquote;
4467 gtk_text_buffer_get_iter_at_offset(
4468 buffer, &startnoquote, noq_offset);
4471 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4472 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4473 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4474 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4475 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4476 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4477 gtk_text_buffer_remove_tag_by_name(
4478 buffer, "quote0", &startnoquote, &endnoquote);
4479 gtk_text_buffer_remove_tag_by_name(
4480 buffer, "quote1", &startnoquote, &endnoquote);
4481 gtk_text_buffer_remove_tag_by_name(
4482 buffer, "quote2", &startnoquote, &endnoquote);
4488 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4489 GtkTextIter nouri_start_iter, nouri_end_iter;
4490 gtk_text_buffer_get_iter_at_offset(
4491 buffer, &nouri_start_iter, nouri_start);
4492 gtk_text_buffer_get_iter_at_offset(
4493 buffer, &nouri_end_iter, nouri_stop);
4494 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4495 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4496 gtk_text_buffer_remove_tag_by_name(
4497 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4498 modified_before_remove = modified;
4503 if (uri_start >= 0 && uri_stop > 0) {
4504 GtkTextIter uri_start_iter, uri_end_iter, back;
4505 gtk_text_buffer_get_iter_at_offset(
4506 buffer, &uri_start_iter, uri_start);
4507 gtk_text_buffer_get_iter_at_offset(
4508 buffer, &uri_end_iter, uri_stop);
4509 back = uri_end_iter;
4510 gtk_text_iter_backward_char(&back);
4511 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4512 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4513 gtk_text_buffer_apply_tag_by_name(
4514 buffer, "link", &uri_start_iter, &uri_end_iter);
4516 if (removed && !modified_before_remove) {
4522 // debug_print("not modified, out after %d lines\n", lines);
4526 // debug_print("modified, out after %d lines\n", lines);
4528 g_free(itemized_chars);
4531 undo_wrapping(compose->undostruct, FALSE);
4532 compose->autowrap = prev_autowrap;
4537 void compose_action_cb(void *data)
4539 Compose *compose = (Compose *)data;
4540 compose_wrap_all(compose);
4543 static void compose_wrap_all(Compose *compose)
4545 compose_wrap_all_full(compose, FALSE);
4548 static void compose_wrap_all_full(Compose *compose, gboolean force)
4550 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4551 GtkTextBuffer *buffer;
4553 gboolean modified = TRUE;
4555 buffer = gtk_text_view_get_buffer(text);
4557 gtk_text_buffer_get_start_iter(buffer, &iter);
4558 while (!gtk_text_iter_is_end(&iter) && modified)
4559 modified = compose_beautify_paragraph(compose, &iter, force);
4563 static void compose_set_title(Compose *compose)
4569 edited = compose->modified ? _(" [Edited]") : "";
4571 subject = gtk_editable_get_chars(
4572 GTK_EDITABLE(compose->subject_entry), 0, -1);
4574 #ifndef GENERIC_UMPC
4575 if (subject && strlen(subject))
4576 str = g_strdup_printf(_("%s - Compose message%s"),
4579 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4581 str = g_strdup(_("Compose message"));
4584 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4590 * compose_current_mail_account:
4592 * Find a current mail account (the currently selected account, or the
4593 * default account, if a news account is currently selected). If a
4594 * mail account cannot be found, display an error message.
4596 * Return value: Mail account, or NULL if not found.
4598 static PrefsAccount *
4599 compose_current_mail_account(void)
4603 if (cur_account && cur_account->protocol != A_NNTP)
4606 ac = account_get_default();
4607 if (!ac || ac->protocol == A_NNTP) {
4608 alertpanel_error(_("Account for sending mail is not specified.\n"
4609 "Please select a mail account before sending."));
4616 #define QUOTE_IF_REQUIRED(out, str) \
4618 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4622 len = strlen(str) + 3; \
4623 if ((__tmp = alloca(len)) == NULL) { \
4624 g_warning("can't allocate memory\n"); \
4625 g_string_free(header, TRUE); \
4628 g_snprintf(__tmp, len, "\"%s\"", str); \
4633 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4634 g_warning("can't allocate memory\n"); \
4635 g_string_free(header, TRUE); \
4638 strcpy(__tmp, str); \
4644 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4646 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4650 len = strlen(str) + 3; \
4651 if ((__tmp = alloca(len)) == NULL) { \
4652 g_warning("can't allocate memory\n"); \
4655 g_snprintf(__tmp, len, "\"%s\"", str); \
4660 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4661 g_warning("can't allocate memory\n"); \
4664 strcpy(__tmp, str); \
4670 static void compose_select_account(Compose *compose, PrefsAccount *account,
4673 gchar *from = NULL, *header;
4674 ComposeHeaderEntry *header_entry;
4676 cm_return_if_fail(account != NULL);
4678 compose->account = account;
4679 if (account->name && *account->name) {
4681 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4682 from = g_strdup_printf("%s <%s>",
4683 buf, account->address);
4684 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4686 from = g_strdup_printf("<%s>",
4688 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4693 compose_set_title(compose);
4695 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4696 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4698 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4699 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4700 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4702 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4704 activate_privacy_system(compose, account, FALSE);
4706 if (!init && compose->mode != COMPOSE_REDIRECT) {
4707 undo_block(compose->undostruct);
4708 compose_insert_sig(compose, TRUE);
4709 undo_unblock(compose->undostruct);
4712 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4713 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4715 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4716 if (account->protocol == A_NNTP) {
4717 if (!strcmp(header, _("To:")))
4718 combobox_select_by_text(
4719 GTK_COMBO_BOX(header_entry->combo),
4722 if (!strcmp(header, _("Newsgroups:")))
4723 combobox_select_by_text(
4724 GTK_COMBO_BOX(header_entry->combo),
4732 /* use account's dict info if set */
4733 if (compose->gtkaspell) {
4734 if (account->enable_default_dictionary)
4735 gtkaspell_change_dict(compose->gtkaspell,
4736 account->default_dictionary, FALSE);
4737 if (account->enable_default_alt_dictionary)
4738 gtkaspell_change_alt_dict(compose->gtkaspell,
4739 account->default_alt_dictionary);
4740 if (account->enable_default_dictionary
4741 || account->enable_default_alt_dictionary)
4742 compose_spell_menu_changed(compose);
4747 gboolean compose_check_for_valid_recipient(Compose *compose) {
4748 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4749 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4750 gboolean recipient_found = FALSE;
4754 /* free to and newsgroup list */
4755 slist_free_strings(compose->to_list);
4756 g_slist_free(compose->to_list);
4757 compose->to_list = NULL;
4759 slist_free_strings(compose->newsgroup_list);
4760 g_slist_free(compose->newsgroup_list);
4761 compose->newsgroup_list = NULL;
4763 /* search header entries for to and newsgroup entries */
4764 for (list = compose->header_list; list; list = list->next) {
4767 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4768 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4771 if (entry[0] != '\0') {
4772 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4773 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4774 compose->to_list = address_list_append(compose->to_list, entry);
4775 recipient_found = TRUE;
4778 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4779 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4780 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4781 recipient_found = TRUE;
4788 return recipient_found;
4791 static gboolean compose_check_for_set_recipients(Compose *compose)
4793 if (compose->account->set_autocc && compose->account->auto_cc) {
4794 gboolean found_other = FALSE;
4796 /* search header entries for to and newsgroup entries */
4797 for (list = compose->header_list; list; list = list->next) {
4800 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4801 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4804 if (strcmp(entry, compose->account->auto_cc)
4805 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4815 if (compose->batch) {
4816 gtk_widget_show_all(compose->window);
4818 aval = alertpanel(_("Send"),
4819 _("The only recipient is the default CC address. Send anyway?"),
4820 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4821 if (aval != G_ALERTALTERNATE)
4825 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4826 gboolean found_other = FALSE;
4828 /* search header entries for to and newsgroup entries */
4829 for (list = compose->header_list; list; list = list->next) {
4832 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4833 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4836 if (strcmp(entry, compose->account->auto_bcc)
4837 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4847 if (compose->batch) {
4848 gtk_widget_show_all(compose->window);
4850 aval = alertpanel(_("Send"),
4851 _("The only recipient is the default BCC address. Send anyway?"),
4852 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4853 if (aval != G_ALERTALTERNATE)
4860 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4864 if (compose_check_for_valid_recipient(compose) == FALSE) {
4865 if (compose->batch) {
4866 gtk_widget_show_all(compose->window);
4868 alertpanel_error(_("Recipient is not specified."));
4872 if (compose_check_for_set_recipients(compose) == FALSE) {
4876 if (!compose->batch) {
4877 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4878 if (*str == '\0' && check_everything == TRUE &&
4879 compose->mode != COMPOSE_REDIRECT) {
4881 gchar *button_label;
4884 if (compose->sending)
4885 button_label = _("+_Send");
4887 button_label = _("+_Queue");
4888 message = g_strdup_printf(_("Subject is empty. %s"),
4889 compose->sending?_("Send it anyway?"):
4890 _("Queue it anyway?"));
4892 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4893 GTK_STOCK_CANCEL, button_label, NULL);
4895 if (aval != G_ALERTALTERNATE)
4900 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4906 gint compose_send(Compose *compose)
4909 FolderItem *folder = NULL;
4911 gchar *msgpath = NULL;
4912 gboolean discard_window = FALSE;
4913 gchar *errstr = NULL;
4914 gchar *tmsgid = NULL;
4915 MainWindow *mainwin = mainwindow_get_mainwindow();
4916 gboolean queued_removed = FALSE;
4918 if (prefs_common.send_dialog_invisible
4919 || compose->batch == TRUE)
4920 discard_window = TRUE;
4922 compose_allow_user_actions (compose, FALSE);
4923 compose->sending = TRUE;
4925 if (compose_check_entries(compose, TRUE) == FALSE) {
4926 if (compose->batch) {
4927 gtk_widget_show_all(compose->window);
4933 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4936 if (compose->batch) {
4937 gtk_widget_show_all(compose->window);
4940 alertpanel_error(_("Could not queue message for sending:\n\n"
4941 "Charset conversion failed."));
4942 } else if (val == -5) {
4943 alertpanel_error(_("Could not queue message for sending:\n\n"
4944 "Couldn't get recipient encryption key."));
4945 } else if (val == -6) {
4947 } else if (val == -3) {
4948 if (privacy_peek_error())
4949 alertpanel_error(_("Could not queue message for sending:\n\n"
4950 "Signature failed: %s"), privacy_get_error());
4951 } else if (val == -2 && errno != 0) {
4952 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4954 alertpanel_error(_("Could not queue message for sending."));
4959 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4960 if (discard_window) {
4961 compose->sending = FALSE;
4962 compose_close(compose);
4963 /* No more compose access in the normal codepath
4964 * after this point! */
4969 alertpanel_error(_("The message was queued but could not be "
4970 "sent.\nUse \"Send queued messages\" from "
4971 "the main window to retry."));
4972 if (!discard_window) {
4979 if (msgpath == NULL) {
4980 msgpath = folder_item_fetch_msg(folder, msgnum);
4981 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4984 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4985 claws_unlink(msgpath);
4988 if (!discard_window) {
4990 if (!queued_removed)
4991 folder_item_remove_msg(folder, msgnum);
4992 folder_item_scan(folder);
4994 /* make sure we delete that */
4995 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4997 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4998 folder_item_remove_msg(folder, tmp->msgnum);
4999 procmsg_msginfo_free(tmp);
5006 if (!queued_removed)
5007 folder_item_remove_msg(folder, msgnum);
5008 folder_item_scan(folder);
5010 /* make sure we delete that */
5011 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5013 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5014 folder_item_remove_msg(folder, tmp->msgnum);
5015 procmsg_msginfo_free(tmp);
5018 if (!discard_window) {
5019 compose->sending = FALSE;
5020 compose_allow_user_actions (compose, TRUE);
5021 compose_close(compose);
5025 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5026 "the main window to retry."), errstr);
5029 alertpanel_error_log(_("The message was queued but could not be "
5030 "sent.\nUse \"Send queued messages\" from "
5031 "the main window to retry."));
5033 if (!discard_window) {
5042 toolbar_main_set_sensitive(mainwin);
5043 main_window_set_menu_sensitive(mainwin);
5049 compose_allow_user_actions (compose, TRUE);
5050 compose->sending = FALSE;
5051 compose->modified = TRUE;
5052 toolbar_main_set_sensitive(mainwin);
5053 main_window_set_menu_sensitive(mainwin);
5058 static gboolean compose_use_attach(Compose *compose)
5060 GtkTreeModel *model = gtk_tree_view_get_model
5061 (GTK_TREE_VIEW(compose->attach_clist));
5062 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5065 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5068 gchar buf[BUFFSIZE];
5070 gboolean first_to_address;
5071 gboolean first_cc_address;
5073 ComposeHeaderEntry *headerentry;
5074 const gchar *headerentryname;
5075 const gchar *cc_hdr;
5076 const gchar *to_hdr;
5077 gboolean err = FALSE;
5079 debug_print("Writing redirect header\n");
5081 cc_hdr = prefs_common_translated_header_name("Cc:");
5082 to_hdr = prefs_common_translated_header_name("To:");
5084 first_to_address = TRUE;
5085 for (list = compose->header_list; list; list = list->next) {
5086 headerentry = ((ComposeHeaderEntry *)list->data);
5087 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5089 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5090 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5091 Xstrdup_a(str, entstr, return -1);
5093 if (str[0] != '\0') {
5094 compose_convert_header
5095 (compose, buf, sizeof(buf), str,
5096 strlen("Resent-To") + 2, TRUE);
5098 if (first_to_address) {
5099 err |= (fprintf(fp, "Resent-To: ") < 0);
5100 first_to_address = FALSE;
5102 err |= (fprintf(fp, ",") < 0);
5104 err |= (fprintf(fp, "%s", buf) < 0);
5108 if (!first_to_address) {
5109 err |= (fprintf(fp, "\n") < 0);
5112 first_cc_address = TRUE;
5113 for (list = compose->header_list; list; list = list->next) {
5114 headerentry = ((ComposeHeaderEntry *)list->data);
5115 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5117 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5118 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5119 Xstrdup_a(str, strg, return -1);
5121 if (str[0] != '\0') {
5122 compose_convert_header
5123 (compose, buf, sizeof(buf), str,
5124 strlen("Resent-Cc") + 2, TRUE);
5126 if (first_cc_address) {
5127 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5128 first_cc_address = FALSE;
5130 err |= (fprintf(fp, ",") < 0);
5132 err |= (fprintf(fp, "%s", buf) < 0);
5136 if (!first_cc_address) {
5137 err |= (fprintf(fp, "\n") < 0);
5140 return (err ? -1:0);
5143 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5145 gchar buf[BUFFSIZE];
5147 const gchar *entstr;
5148 /* struct utsname utsbuf; */
5149 gboolean err = FALSE;
5151 cm_return_val_if_fail(fp != NULL, -1);
5152 cm_return_val_if_fail(compose->account != NULL, -1);
5153 cm_return_val_if_fail(compose->account->address != NULL, -1);
5156 get_rfc822_date(buf, sizeof(buf));
5157 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5160 if (compose->account->name && *compose->account->name) {
5161 compose_convert_header
5162 (compose, buf, sizeof(buf), compose->account->name,
5163 strlen("From: "), TRUE);
5164 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5165 buf, compose->account->address) < 0);
5167 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5170 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5171 if (*entstr != '\0') {
5172 Xstrdup_a(str, entstr, return -1);
5175 compose_convert_header(compose, buf, sizeof(buf), str,
5176 strlen("Subject: "), FALSE);
5177 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5181 /* Resent-Message-ID */
5182 if (compose->account->set_domain && compose->account->domain) {
5183 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5184 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5185 g_snprintf(buf, sizeof(buf), "%s",
5186 strchr(compose->account->address, '@') ?
5187 strchr(compose->account->address, '@')+1 :
5188 compose->account->address);
5190 g_snprintf(buf, sizeof(buf), "%s", "");
5193 if (compose->account->gen_msgid) {
5195 if (compose->account->msgid_with_addr) {
5196 addr = compose->account->address;
5198 generate_msgid(buf, sizeof(buf), addr);
5199 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5200 compose->msgid = g_strdup(buf);
5202 compose->msgid = NULL;
5205 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5208 /* separator between header and body */
5209 err |= (fputs("\n", fp) == EOF);
5211 return (err ? -1:0);
5214 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5218 gchar buf[BUFFSIZE];
5220 gboolean skip = FALSE;
5221 gboolean err = FALSE;
5222 gchar *not_included[]={
5223 "Return-Path:", "Delivered-To:", "Received:",
5224 "Subject:", "X-UIDL:", "AF:",
5225 "NF:", "PS:", "SRH:",
5226 "SFN:", "DSR:", "MID:",
5227 "CFG:", "PT:", "S:",
5228 "RQ:", "SSV:", "NSV:",
5229 "SSH:", "R:", "MAID:",
5230 "NAID:", "RMID:", "FMID:",
5231 "SCF:", "RRCPT:", "NG:",
5232 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5233 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5234 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5235 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5236 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5239 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5240 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5244 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5246 for (i = 0; not_included[i] != NULL; i++) {
5247 if (g_ascii_strncasecmp(buf, not_included[i],
5248 strlen(not_included[i])) == 0) {
5255 if (fputs(buf, fdest) == -1)
5258 if (!prefs_common.redirect_keep_from) {
5259 if (g_ascii_strncasecmp(buf, "From:",
5260 strlen("From:")) == 0) {
5261 err |= (fputs(" (by way of ", fdest) == EOF);
5262 if (compose->account->name
5263 && *compose->account->name) {
5264 compose_convert_header
5265 (compose, buf, sizeof(buf),
5266 compose->account->name,
5269 err |= (fprintf(fdest, "%s <%s>",
5271 compose->account->address) < 0);
5273 err |= (fprintf(fdest, "%s",
5274 compose->account->address) < 0);
5275 err |= (fputs(")", fdest) == EOF);
5279 if (fputs("\n", fdest) == -1)
5286 if (compose_redirect_write_headers(compose, fdest))
5289 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5290 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5303 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5305 GtkTextBuffer *buffer;
5306 GtkTextIter start, end;
5309 const gchar *out_codeset;
5310 EncodingType encoding = ENC_UNKNOWN;
5311 MimeInfo *mimemsg, *mimetext;
5313 const gchar *src_codeset = CS_INTERNAL;
5314 gchar *from_addr = NULL;
5315 gchar *from_name = NULL;
5317 if (action == COMPOSE_WRITE_FOR_SEND)
5318 attach_parts = TRUE;
5320 /* create message MimeInfo */
5321 mimemsg = procmime_mimeinfo_new();
5322 mimemsg->type = MIMETYPE_MESSAGE;
5323 mimemsg->subtype = g_strdup("rfc822");
5324 mimemsg->content = MIMECONTENT_MEM;
5325 mimemsg->tmp = TRUE; /* must free content later */
5326 mimemsg->data.mem = compose_get_header(compose);
5328 /* Create text part MimeInfo */
5329 /* get all composed text */
5330 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5331 gtk_text_buffer_get_start_iter(buffer, &start);
5332 gtk_text_buffer_get_end_iter(buffer, &end);
5333 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5335 out_codeset = conv_get_charset_str(compose->out_encoding);
5337 if (!out_codeset && is_ascii_str(chars)) {
5338 out_codeset = CS_US_ASCII;
5339 } else if (prefs_common.outgoing_fallback_to_ascii &&
5340 is_ascii_str(chars)) {
5341 out_codeset = CS_US_ASCII;
5342 encoding = ENC_7BIT;
5346 gchar *test_conv_global_out = NULL;
5347 gchar *test_conv_reply = NULL;
5349 /* automatic mode. be automatic. */
5350 codeconv_set_strict(TRUE);
5352 out_codeset = conv_get_outgoing_charset_str();
5354 debug_print("trying to convert to %s\n", out_codeset);
5355 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5358 if (!test_conv_global_out && compose->orig_charset
5359 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5360 out_codeset = compose->orig_charset;
5361 debug_print("failure; trying to convert to %s\n", out_codeset);
5362 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5365 if (!test_conv_global_out && !test_conv_reply) {
5367 out_codeset = CS_INTERNAL;
5368 debug_print("failure; finally using %s\n", out_codeset);
5370 g_free(test_conv_global_out);
5371 g_free(test_conv_reply);
5372 codeconv_set_strict(FALSE);
5375 if (encoding == ENC_UNKNOWN) {
5376 if (prefs_common.encoding_method == CTE_BASE64)
5377 encoding = ENC_BASE64;
5378 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5379 encoding = ENC_QUOTED_PRINTABLE;
5380 else if (prefs_common.encoding_method == CTE_8BIT)
5381 encoding = ENC_8BIT;
5383 encoding = procmime_get_encoding_for_charset(out_codeset);
5386 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5387 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5389 if (action == COMPOSE_WRITE_FOR_SEND) {
5390 codeconv_set_strict(TRUE);
5391 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5392 codeconv_set_strict(FALSE);
5398 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5399 "to the specified %s charset.\n"
5400 "Send it as %s?"), out_codeset, src_codeset);
5401 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5402 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5405 if (aval != G_ALERTALTERNATE) {
5410 out_codeset = src_codeset;
5416 out_codeset = src_codeset;
5421 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5422 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5423 strstr(buf, "\nFrom ") != NULL) {
5424 encoding = ENC_QUOTED_PRINTABLE;
5428 mimetext = procmime_mimeinfo_new();
5429 mimetext->content = MIMECONTENT_MEM;
5430 mimetext->tmp = TRUE; /* must free content later */
5431 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5432 * and free the data, which we need later. */
5433 mimetext->data.mem = g_strdup(buf);
5434 mimetext->type = MIMETYPE_TEXT;
5435 mimetext->subtype = g_strdup("plain");
5436 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5437 g_strdup(out_codeset));
5439 /* protect trailing spaces when signing message */
5440 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5441 privacy_system_can_sign(compose->privacy_system)) {
5442 encoding = ENC_QUOTED_PRINTABLE;
5445 debug_print("main text: %zd bytes encoded as %s in %d\n",
5446 strlen(buf), out_codeset, encoding);
5448 /* check for line length limit */
5449 if (action == COMPOSE_WRITE_FOR_SEND &&
5450 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5451 check_line_length(buf, 1000, &line) < 0) {
5455 msg = g_strdup_printf
5456 (_("Line %d exceeds the line length limit (998 bytes).\n"
5457 "The contents of the message might be broken on the way to the delivery.\n"
5459 "Send it anyway?"), line + 1);
5460 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5462 if (aval != G_ALERTALTERNATE) {
5468 if (encoding != ENC_UNKNOWN)
5469 procmime_encode_content(mimetext, encoding);
5471 /* append attachment parts */
5472 if (compose_use_attach(compose) && attach_parts) {
5473 MimeInfo *mimempart;
5474 gchar *boundary = NULL;
5475 mimempart = procmime_mimeinfo_new();
5476 mimempart->content = MIMECONTENT_EMPTY;
5477 mimempart->type = MIMETYPE_MULTIPART;
5478 mimempart->subtype = g_strdup("mixed");
5482 boundary = generate_mime_boundary(NULL);
5483 } while (strstr(buf, boundary) != NULL);
5485 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5488 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5490 g_node_append(mimempart->node, mimetext->node);
5491 g_node_append(mimemsg->node, mimempart->node);
5493 if (compose_add_attachments(compose, mimempart) < 0)
5496 g_node_append(mimemsg->node, mimetext->node);
5500 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5501 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5502 /* extract name and address */
5503 if (strstr(spec, " <") && strstr(spec, ">")) {
5504 from_addr = g_strdup(strrchr(spec, '<')+1);
5505 *(strrchr(from_addr, '>')) = '\0';
5506 from_name = g_strdup(spec);
5507 *(strrchr(from_name, '<')) = '\0';
5514 /* sign message if sending */
5515 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5516 privacy_system_can_sign(compose->privacy_system))
5517 if (!privacy_sign(compose->privacy_system, mimemsg,
5518 compose->account, from_addr)) {
5525 procmime_write_mimeinfo(mimemsg, fp);
5527 procmime_mimeinfo_free_all(mimemsg);
5532 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5534 GtkTextBuffer *buffer;
5535 GtkTextIter start, end;
5540 if ((fp = g_fopen(file, "wb")) == NULL) {
5541 FILE_OP_ERROR(file, "fopen");
5545 /* chmod for security */
5546 if (change_file_mode_rw(fp, file) < 0) {
5547 FILE_OP_ERROR(file, "chmod");
5548 g_warning("can't change file mode\n");
5551 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5552 gtk_text_buffer_get_start_iter(buffer, &start);
5553 gtk_text_buffer_get_end_iter(buffer, &end);
5554 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5556 chars = conv_codeset_strdup
5557 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5560 if (!chars) return -1;
5563 len = strlen(chars);
5564 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5565 FILE_OP_ERROR(file, "fwrite");
5574 if (fclose(fp) == EOF) {
5575 FILE_OP_ERROR(file, "fclose");
5582 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5585 MsgInfo *msginfo = compose->targetinfo;
5587 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5588 if (!msginfo) return -1;
5590 if (!force && MSG_IS_LOCKED(msginfo->flags))
5593 item = msginfo->folder;
5594 cm_return_val_if_fail(item != NULL, -1);
5596 if (procmsg_msg_exist(msginfo) &&
5597 (folder_has_parent_of_type(item, F_QUEUE) ||
5598 folder_has_parent_of_type(item, F_DRAFT)
5599 || msginfo == compose->autosaved_draft)) {
5600 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5601 g_warning("can't remove the old message\n");
5604 debug_print("removed reedit target %d\n", msginfo->msgnum);
5611 static void compose_remove_draft(Compose *compose)
5614 MsgInfo *msginfo = compose->targetinfo;
5615 drafts = account_get_special_folder(compose->account, F_DRAFT);
5617 if (procmsg_msg_exist(msginfo)) {
5618 folder_item_remove_msg(drafts, msginfo->msgnum);
5623 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5624 gboolean remove_reedit_target)
5626 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5629 static gboolean compose_warn_encryption(Compose *compose)
5631 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5632 AlertValue val = G_ALERTALTERNATE;
5634 if (warning == NULL)
5637 val = alertpanel_full(_("Encryption warning"), warning,
5638 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5639 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5640 if (val & G_ALERTDISABLE) {
5641 val &= ~G_ALERTDISABLE;
5642 if (val == G_ALERTALTERNATE)
5643 privacy_inhibit_encrypt_warning(compose->privacy_system,
5647 if (val == G_ALERTALTERNATE) {
5654 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5655 gchar **msgpath, gboolean check_subject,
5656 gboolean remove_reedit_target)
5663 static gboolean lock = FALSE;
5664 PrefsAccount *mailac = NULL, *newsac = NULL;
5665 gboolean err = FALSE;
5667 debug_print("queueing message...\n");
5668 cm_return_val_if_fail(compose->account != NULL, -1);
5672 if (compose_check_entries(compose, check_subject) == FALSE) {
5674 if (compose->batch) {
5675 gtk_widget_show_all(compose->window);
5680 if (!compose->to_list && !compose->newsgroup_list) {
5681 g_warning("can't get recipient list.");
5686 if (compose->to_list) {
5687 if (compose->account->protocol != A_NNTP)
5688 mailac = compose->account;
5689 else if (cur_account && cur_account->protocol != A_NNTP)
5690 mailac = cur_account;
5691 else if (!(mailac = compose_current_mail_account())) {
5693 alertpanel_error(_("No account for sending mails available!"));
5698 if (compose->newsgroup_list) {
5699 if (compose->account->protocol == A_NNTP)
5700 newsac = compose->account;
5703 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5708 /* write queue header */
5709 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5710 G_DIR_SEPARATOR, compose, (guint) rand());
5711 debug_print("queuing to %s\n", tmp);
5712 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5713 FILE_OP_ERROR(tmp, "fopen");
5719 if (change_file_mode_rw(fp, tmp) < 0) {
5720 FILE_OP_ERROR(tmp, "chmod");
5721 g_warning("can't change file mode\n");
5724 /* queueing variables */
5725 err |= (fprintf(fp, "AF:\n") < 0);
5726 err |= (fprintf(fp, "NF:0\n") < 0);
5727 err |= (fprintf(fp, "PS:10\n") < 0);
5728 err |= (fprintf(fp, "SRH:1\n") < 0);
5729 err |= (fprintf(fp, "SFN:\n") < 0);
5730 err |= (fprintf(fp, "DSR:\n") < 0);
5732 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5734 err |= (fprintf(fp, "MID:\n") < 0);
5735 err |= (fprintf(fp, "CFG:\n") < 0);
5736 err |= (fprintf(fp, "PT:0\n") < 0);
5737 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5738 err |= (fprintf(fp, "RQ:\n") < 0);
5740 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5742 err |= (fprintf(fp, "SSV:\n") < 0);
5744 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5746 err |= (fprintf(fp, "NSV:\n") < 0);
5747 err |= (fprintf(fp, "SSH:\n") < 0);
5748 /* write recepient list */
5749 if (compose->to_list) {
5750 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5751 for (cur = compose->to_list->next; cur != NULL;
5753 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5754 err |= (fprintf(fp, "\n") < 0);
5756 /* write newsgroup list */
5757 if (compose->newsgroup_list) {
5758 err |= (fprintf(fp, "NG:") < 0);
5759 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5760 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5761 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5762 err |= (fprintf(fp, "\n") < 0);
5764 /* Sylpheed account IDs */
5766 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5768 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5771 if (compose->privacy_system != NULL) {
5772 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5773 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5774 if (compose->use_encryption) {
5776 if (!compose_warn_encryption(compose)) {
5783 if (mailac && mailac->encrypt_to_self) {
5784 GSList *tmp_list = g_slist_copy(compose->to_list);
5785 tmp_list = g_slist_append(tmp_list, compose->account->address);
5786 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5787 g_slist_free(tmp_list);
5789 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5791 if (encdata != NULL) {
5792 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5793 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5794 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5796 } /* else we finally dont want to encrypt */
5798 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5799 /* and if encdata was null, it means there's been a problem in
5811 /* Save copy folder */
5812 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5813 gchar *savefolderid;
5815 savefolderid = compose_get_save_to(compose);
5816 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5817 g_free(savefolderid);
5819 /* Save copy folder */
5820 if (compose->return_receipt) {
5821 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5823 /* Message-ID of message replying to */
5824 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5827 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5828 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5831 /* Message-ID of message forwarding to */
5832 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5835 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5836 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5840 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5841 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5843 /* end of headers */
5844 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5846 if (compose->redirect_filename != NULL) {
5847 if (compose_redirect_write_to_file(compose, fp) < 0) {
5856 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5861 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5865 g_warning("failed to write queue message\n");
5872 if (fclose(fp) == EOF) {
5873 FILE_OP_ERROR(tmp, "fclose");
5880 if (item && *item) {
5883 queue = account_get_special_folder(compose->account, F_QUEUE);
5886 g_warning("can't find queue folder\n");
5892 folder_item_scan(queue);
5893 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5894 g_warning("can't queue the message\n");
5901 if (msgpath == NULL) {
5907 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5908 compose_remove_reedit_target(compose, FALSE);
5911 if ((msgnum != NULL) && (item != NULL)) {
5919 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5922 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5924 struct stat statbuf;
5925 gchar *type, *subtype;
5926 GtkTreeModel *model;
5929 model = gtk_tree_view_get_model(tree_view);
5931 if (!gtk_tree_model_get_iter_first(model, &iter))
5934 gtk_tree_model_get(model, &iter,
5938 if (!is_file_exist(ainfo->file)) {
5939 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5940 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5941 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5943 if (val == G_ALERTDEFAULT) {
5948 mimepart = procmime_mimeinfo_new();
5949 mimepart->content = MIMECONTENT_FILE;
5950 mimepart->data.filename = g_strdup(ainfo->file);
5951 mimepart->tmp = FALSE; /* or we destroy our attachment */
5952 mimepart->offset = 0;
5954 g_stat(ainfo->file, &statbuf);
5955 mimepart->length = statbuf.st_size;
5957 type = g_strdup(ainfo->content_type);
5959 if (!strchr(type, '/')) {
5961 type = g_strdup("application/octet-stream");
5964 subtype = strchr(type, '/') + 1;
5965 *(subtype - 1) = '\0';
5966 mimepart->type = procmime_get_media_type(type);
5967 mimepart->subtype = g_strdup(subtype);
5970 if (mimepart->type == MIMETYPE_MESSAGE &&
5971 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5972 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5975 if (mimepart->type == MIMETYPE_APPLICATION &&
5976 !strcmp2(mimepart->subtype, "octet-stream"))
5977 g_hash_table_insert(mimepart->typeparameters,
5978 g_strdup("name"), g_strdup(ainfo->name));
5979 g_hash_table_insert(mimepart->dispositionparameters,
5980 g_strdup("filename"), g_strdup(ainfo->name));
5981 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5985 if (compose->use_signing) {
5986 if (ainfo->encoding == ENC_7BIT)
5987 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5988 else if (ainfo->encoding == ENC_8BIT)
5989 ainfo->encoding = ENC_BASE64;
5992 procmime_encode_content(mimepart, ainfo->encoding);
5994 g_node_append(parent->node, mimepart->node);
5995 } while (gtk_tree_model_iter_next(model, &iter));
6000 #define IS_IN_CUSTOM_HEADER(header) \
6001 (compose->account->add_customhdr && \
6002 custom_header_find(compose->account->customhdr_list, header) != NULL)
6004 static void compose_add_headerfield_from_headerlist(Compose *compose,
6006 const gchar *fieldname,
6007 const gchar *seperator)
6009 gchar *str, *fieldname_w_colon;
6010 gboolean add_field = FALSE;
6012 ComposeHeaderEntry *headerentry;
6013 const gchar *headerentryname;
6014 const gchar *trans_fieldname;
6017 if (IS_IN_CUSTOM_HEADER(fieldname))
6020 debug_print("Adding %s-fields\n", fieldname);
6022 fieldstr = g_string_sized_new(64);
6024 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6025 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6027 for (list = compose->header_list; list; list = list->next) {
6028 headerentry = ((ComposeHeaderEntry *)list->data);
6029 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6031 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6032 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6034 if (str[0] != '\0') {
6036 g_string_append(fieldstr, seperator);
6037 g_string_append(fieldstr, str);
6046 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6047 compose_convert_header
6048 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6049 strlen(fieldname) + 2, TRUE);
6050 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6054 g_free(fieldname_w_colon);
6055 g_string_free(fieldstr, TRUE);
6060 static gchar *compose_get_header(Compose *compose)
6062 gchar buf[BUFFSIZE];
6063 const gchar *entry_str;
6067 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6069 gchar *from_name = NULL, *from_address = NULL;
6072 cm_return_val_if_fail(compose->account != NULL, NULL);
6073 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6075 header = g_string_sized_new(64);
6078 get_rfc822_date(buf, sizeof(buf));
6079 g_string_append_printf(header, "Date: %s\n", buf);
6083 if (compose->account->name && *compose->account->name) {
6085 QUOTE_IF_REQUIRED(buf, compose->account->name);
6086 tmp = g_strdup_printf("%s <%s>",
6087 buf, compose->account->address);
6089 tmp = g_strdup_printf("%s",
6090 compose->account->address);
6092 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6093 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6095 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6096 from_address = g_strdup(compose->account->address);
6098 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6099 /* extract name and address */
6100 if (strstr(spec, " <") && strstr(spec, ">")) {
6101 from_address = g_strdup(strrchr(spec, '<')+1);
6102 *(strrchr(from_address, '>')) = '\0';
6103 from_name = g_strdup(spec);
6104 *(strrchr(from_name, '<')) = '\0';
6107 from_address = g_strdup(spec);
6114 if (from_name && *from_name) {
6115 compose_convert_header
6116 (compose, buf, sizeof(buf), from_name,
6117 strlen("From: "), TRUE);
6118 QUOTE_IF_REQUIRED(name, buf);
6120 g_string_append_printf(header, "From: %s <%s>\n",
6121 name, from_address);
6123 g_string_append_printf(header, "From: %s\n", from_address);
6126 g_free(from_address);
6129 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6132 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6135 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6139 * If this account is a NNTP account remove Bcc header from
6140 * message body since it otherwise will be publicly shown
6142 if (compose->account->protocol != A_NNTP)
6143 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6146 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6148 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6151 compose_convert_header(compose, buf, sizeof(buf), str,
6152 strlen("Subject: "), FALSE);
6153 g_string_append_printf(header, "Subject: %s\n", buf);
6159 if (compose->account->set_domain && compose->account->domain) {
6160 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6161 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6162 g_snprintf(buf, sizeof(buf), "%s",
6163 strchr(compose->account->address, '@') ?
6164 strchr(compose->account->address, '@')+1 :
6165 compose->account->address);
6167 g_snprintf(buf, sizeof(buf), "%s", "");
6170 if (compose->account->gen_msgid) {
6172 if (compose->account->msgid_with_addr) {
6173 addr = compose->account->address;
6175 generate_msgid(buf, sizeof(buf), addr);
6176 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6177 compose->msgid = g_strdup(buf);
6179 compose->msgid = NULL;
6182 if (compose->remove_references == FALSE) {
6184 if (compose->inreplyto && compose->to_list)
6185 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6188 if (compose->references)
6189 g_string_append_printf(header, "References: %s\n", compose->references);
6193 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6196 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6199 if (compose->account->organization &&
6200 strlen(compose->account->organization) &&
6201 !IS_IN_CUSTOM_HEADER("Organization")) {
6202 compose_convert_header(compose, buf, sizeof(buf),
6203 compose->account->organization,
6204 strlen("Organization: "), FALSE);
6205 g_string_append_printf(header, "Organization: %s\n", buf);
6208 /* Program version and system info */
6209 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6210 !compose->newsgroup_list) {
6211 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6213 gtk_major_version, gtk_minor_version, gtk_micro_version,
6216 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6217 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6219 gtk_major_version, gtk_minor_version, gtk_micro_version,
6223 /* custom headers */
6224 if (compose->account->add_customhdr) {
6227 for (cur = compose->account->customhdr_list; cur != NULL;
6229 CustomHeader *chdr = (CustomHeader *)cur->data;
6231 if (custom_header_is_allowed(chdr->name)) {
6232 compose_convert_header
6233 (compose, buf, sizeof(buf),
6234 chdr->value ? chdr->value : "",
6235 strlen(chdr->name) + 2, FALSE);
6236 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6241 /* Automatic Faces and X-Faces */
6242 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6243 g_string_append_printf(header, "X-Face: %s\n", buf);
6245 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6246 g_string_append_printf(header, "X-Face: %s\n", buf);
6248 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6249 g_string_append_printf(header, "Face: %s\n", buf);
6251 else if (get_default_face (buf, sizeof(buf)) == 0) {
6252 g_string_append_printf(header, "Face: %s\n", buf);
6256 switch (compose->priority) {
6257 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6258 "X-Priority: 1 (Highest)\n");
6260 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6261 "X-Priority: 2 (High)\n");
6263 case PRIORITY_NORMAL: break;
6264 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6265 "X-Priority: 4 (Low)\n");
6267 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6268 "X-Priority: 5 (Lowest)\n");
6270 default: debug_print("compose: priority unknown : %d\n",
6274 /* Request Return Receipt */
6275 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6276 if (compose->return_receipt) {
6277 if (compose->account->name
6278 && *compose->account->name) {
6279 compose_convert_header(compose, buf, sizeof(buf),
6280 compose->account->name,
6281 strlen("Disposition-Notification-To: "),
6283 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6285 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6289 /* get special headers */
6290 for (list = compose->header_list; list; list = list->next) {
6291 ComposeHeaderEntry *headerentry;
6294 gchar *headername_wcolon;
6295 const gchar *headername_trans;
6298 gboolean standard_header = FALSE;
6300 headerentry = ((ComposeHeaderEntry *)list->data);
6302 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6304 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6309 if (!strstr(tmp, ":")) {
6310 headername_wcolon = g_strconcat(tmp, ":", NULL);
6311 headername = g_strdup(tmp);
6313 headername_wcolon = g_strdup(tmp);
6314 headername = g_strdup(strtok(tmp, ":"));
6318 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6319 Xstrdup_a(headervalue, entry_str, return NULL);
6320 subst_char(headervalue, '\r', ' ');
6321 subst_char(headervalue, '\n', ' ');
6322 string = std_headers;
6323 while (*string != NULL) {
6324 headername_trans = prefs_common_translated_header_name(*string);
6325 if (!strcmp(headername_trans, headername_wcolon))
6326 standard_header = TRUE;
6329 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6330 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6333 g_free(headername_wcolon);
6337 g_string_free(header, FALSE);
6342 #undef IS_IN_CUSTOM_HEADER
6344 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6345 gint header_len, gboolean addr_field)
6347 gchar *tmpstr = NULL;
6348 const gchar *out_codeset = NULL;
6350 cm_return_if_fail(src != NULL);
6351 cm_return_if_fail(dest != NULL);
6353 if (len < 1) return;
6355 tmpstr = g_strdup(src);
6357 subst_char(tmpstr, '\n', ' ');
6358 subst_char(tmpstr, '\r', ' ');
6361 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6362 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6363 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6368 codeconv_set_strict(TRUE);
6369 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6370 conv_get_charset_str(compose->out_encoding));
6371 codeconv_set_strict(FALSE);
6373 if (!dest || *dest == '\0') {
6374 gchar *test_conv_global_out = NULL;
6375 gchar *test_conv_reply = NULL;
6377 /* automatic mode. be automatic. */
6378 codeconv_set_strict(TRUE);
6380 out_codeset = conv_get_outgoing_charset_str();
6382 debug_print("trying to convert to %s\n", out_codeset);
6383 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6386 if (!test_conv_global_out && compose->orig_charset
6387 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6388 out_codeset = compose->orig_charset;
6389 debug_print("failure; trying to convert to %s\n", out_codeset);
6390 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6393 if (!test_conv_global_out && !test_conv_reply) {
6395 out_codeset = CS_INTERNAL;
6396 debug_print("finally using %s\n", out_codeset);
6398 g_free(test_conv_global_out);
6399 g_free(test_conv_reply);
6400 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6402 codeconv_set_strict(FALSE);
6407 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6411 cm_return_if_fail(user_data != NULL);
6413 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6414 g_strstrip(address);
6415 if (*address != '\0') {
6416 gchar *name = procheader_get_fromname(address);
6417 extract_address(address);
6418 addressbook_add_contact(name, address, NULL, NULL);
6423 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6425 GtkWidget *menuitem;
6428 cm_return_if_fail(menu != NULL);
6429 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6431 menuitem = gtk_separator_menu_item_new();
6432 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6433 gtk_widget_show(menuitem);
6435 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6436 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6438 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6439 g_strstrip(address);
6440 if (*address == '\0') {
6441 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6444 g_signal_connect(G_OBJECT(menuitem), "activate",
6445 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6446 gtk_widget_show(menuitem);
6449 static void compose_create_header_entry(Compose *compose)
6451 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6458 const gchar *header = NULL;
6459 ComposeHeaderEntry *headerentry;
6460 gboolean standard_header = FALSE;
6461 GtkListStore *model;
6463 #if !(GTK_CHECK_VERSION(2,12,0))
6464 GtkTooltips *tips = compose->tooltips;
6467 headerentry = g_new0(ComposeHeaderEntry, 1);
6470 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6471 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6472 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6474 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6476 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6478 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6479 COMPOSE_NEWSGROUPS);
6480 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6482 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6483 COMPOSE_FOLLOWUPTO);
6485 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6486 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6487 G_CALLBACK(compose_grab_focus_cb), compose);
6488 gtk_widget_show(combo);
6489 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6490 compose->header_nextrow, compose->header_nextrow+1,
6491 GTK_SHRINK, GTK_FILL, 0, 0);
6492 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6493 const gchar *last_header_entry = gtk_entry_get_text(
6494 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6496 while (*string != NULL) {
6497 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6498 standard_header = TRUE;
6501 if (standard_header)
6502 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6504 if (!compose->header_last || !standard_header) {
6505 switch(compose->account->protocol) {
6507 header = prefs_common_translated_header_name("Newsgroups:");
6510 header = prefs_common_translated_header_name("To:");
6515 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6517 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6518 G_CALLBACK(compose_grab_focus_cb), compose);
6520 /* Entry field with cleanup button */
6521 button = gtk_button_new();
6522 gtk_button_set_image(GTK_BUTTON(button),
6523 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6524 gtk_widget_show(button);
6525 CLAWS_SET_TIP(button,
6526 _("Delete entry contents"));
6527 entry = gtk_entry_new();
6528 gtk_widget_show(entry);
6529 CLAWS_SET_TIP(entry,
6530 _("Use <tab> to autocomplete from addressbook"));
6531 hbox = gtk_hbox_new (FALSE, 0);
6532 gtk_widget_show(hbox);
6533 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6534 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6535 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6536 compose->header_nextrow, compose->header_nextrow+1,
6537 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6539 g_signal_connect(G_OBJECT(entry), "key-press-event",
6540 G_CALLBACK(compose_headerentry_key_press_event_cb),
6542 g_signal_connect(G_OBJECT(entry), "changed",
6543 G_CALLBACK(compose_headerentry_changed_cb),
6545 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6546 G_CALLBACK(compose_grab_focus_cb), compose);
6548 g_signal_connect(G_OBJECT(button), "clicked",
6549 G_CALLBACK(compose_headerentry_button_clicked_cb),
6553 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6554 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6555 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6556 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6557 G_CALLBACK(compose_header_drag_received_cb),
6559 g_signal_connect(G_OBJECT(entry), "drag-drop",
6560 G_CALLBACK(compose_drag_drop),
6562 g_signal_connect(G_OBJECT(entry), "populate-popup",
6563 G_CALLBACK(compose_entry_popup_extend),
6566 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6568 headerentry->compose = compose;
6569 headerentry->combo = combo;
6570 headerentry->entry = entry;
6571 headerentry->button = button;
6572 headerentry->hbox = hbox;
6573 headerentry->headernum = compose->header_nextrow;
6574 headerentry->type = PREF_NONE;
6576 compose->header_nextrow++;
6577 compose->header_last = headerentry;
6578 compose->header_list =
6579 g_slist_append(compose->header_list,
6583 static void compose_add_header_entry(Compose *compose, const gchar *header,
6584 gchar *text, ComposePrefType pref_type)
6586 ComposeHeaderEntry *last_header = compose->header_last;
6587 gchar *tmp = g_strdup(text), *email;
6588 gboolean replyto_hdr;
6590 replyto_hdr = (!strcasecmp(header,
6591 prefs_common_translated_header_name("Reply-To:")) ||
6593 prefs_common_translated_header_name("Followup-To:")) ||
6595 prefs_common_translated_header_name("In-Reply-To:")));
6597 extract_address(tmp);
6598 email = g_utf8_strdown(tmp, -1);
6600 if (replyto_hdr == FALSE &&
6601 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6603 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6604 header, text, (gint) pref_type);
6610 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6611 gtk_entry_set_text(GTK_ENTRY(
6612 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6614 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6615 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6616 last_header->type = pref_type;
6618 if (replyto_hdr == FALSE)
6619 g_hash_table_insert(compose->email_hashtable, email,
6620 GUINT_TO_POINTER(1));
6627 static void compose_destroy_headerentry(Compose *compose,
6628 ComposeHeaderEntry *headerentry)
6630 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6633 extract_address(text);
6634 email = g_utf8_strdown(text, -1);
6635 g_hash_table_remove(compose->email_hashtable, email);
6639 gtk_widget_destroy(headerentry->combo);
6640 gtk_widget_destroy(headerentry->entry);
6641 gtk_widget_destroy(headerentry->button);
6642 gtk_widget_destroy(headerentry->hbox);
6643 g_free(headerentry);
6646 static void compose_remove_header_entries(Compose *compose)
6649 for (list = compose->header_list; list; list = list->next)
6650 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6652 compose->header_last = NULL;
6653 g_slist_free(compose->header_list);
6654 compose->header_list = NULL;
6655 compose->header_nextrow = 1;
6656 compose_create_header_entry(compose);
6659 static GtkWidget *compose_create_header(Compose *compose)
6661 GtkWidget *from_optmenu_hbox;
6662 GtkWidget *header_scrolledwin;
6663 GtkWidget *header_table;
6667 /* header labels and entries */
6668 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6669 gtk_widget_show(header_scrolledwin);
6670 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6672 header_table = gtk_table_new(2, 2, FALSE);
6673 gtk_widget_show(header_table);
6674 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6675 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6676 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6679 /* option menu for selecting accounts */
6680 from_optmenu_hbox = compose_account_option_menu_create(compose);
6681 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6682 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6685 compose->header_table = header_table;
6686 compose->header_list = NULL;
6687 compose->header_nextrow = count;
6689 compose_create_header_entry(compose);
6691 compose->table = NULL;
6693 return header_scrolledwin ;
6696 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6698 Compose *compose = (Compose *)data;
6699 GdkEventButton event;
6702 event.time = gtk_get_current_event_time();
6704 return attach_button_pressed(compose->attach_clist, &event, compose);
6707 static GtkWidget *compose_create_attach(Compose *compose)
6709 GtkWidget *attach_scrwin;
6710 GtkWidget *attach_clist;
6712 GtkListStore *store;
6713 GtkCellRenderer *renderer;
6714 GtkTreeViewColumn *column;
6715 GtkTreeSelection *selection;
6717 /* attachment list */
6718 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6719 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6720 GTK_POLICY_AUTOMATIC,
6721 GTK_POLICY_AUTOMATIC);
6722 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6724 store = gtk_list_store_new(N_ATTACH_COLS,
6729 G_TYPE_AUTO_POINTER,
6731 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6732 (GTK_TREE_MODEL(store)));
6733 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6734 g_object_unref(store);
6736 renderer = gtk_cell_renderer_text_new();
6737 column = gtk_tree_view_column_new_with_attributes
6738 (_("Mime type"), renderer, "text",
6739 COL_MIMETYPE, NULL);
6740 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6742 renderer = gtk_cell_renderer_text_new();
6743 column = gtk_tree_view_column_new_with_attributes
6744 (_("Size"), renderer, "text",
6746 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6748 renderer = gtk_cell_renderer_text_new();
6749 column = gtk_tree_view_column_new_with_attributes
6750 (_("Name"), renderer, "text",
6752 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6754 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6755 prefs_common.use_stripes_everywhere);
6756 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6757 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6759 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6760 G_CALLBACK(attach_selected), compose);
6761 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6762 G_CALLBACK(attach_button_pressed), compose);
6764 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6765 G_CALLBACK(popup_attach_button_pressed), compose);
6767 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6768 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6769 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6770 G_CALLBACK(popup_attach_button_pressed), compose);
6772 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6773 G_CALLBACK(attach_key_pressed), compose);
6776 gtk_drag_dest_set(attach_clist,
6777 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6778 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6779 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6780 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6781 G_CALLBACK(compose_attach_drag_received_cb),
6783 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6784 G_CALLBACK(compose_drag_drop),
6787 compose->attach_scrwin = attach_scrwin;
6788 compose->attach_clist = attach_clist;
6790 return attach_scrwin;
6793 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6794 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6796 static GtkWidget *compose_create_others(Compose *compose)
6799 GtkWidget *savemsg_checkbtn;
6800 GtkWidget *savemsg_combo;
6801 GtkWidget *savemsg_select;
6804 gchar *folderidentifier;
6806 /* Table for settings */
6807 table = gtk_table_new(3, 1, FALSE);
6808 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6809 gtk_widget_show(table);
6810 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6813 /* Save Message to folder */
6814 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6815 gtk_widget_show(savemsg_checkbtn);
6816 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6817 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6818 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6820 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6821 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6823 savemsg_combo = gtk_combo_box_entry_new_text();
6824 compose->savemsg_checkbtn = savemsg_checkbtn;
6825 compose->savemsg_combo = savemsg_combo;
6826 gtk_widget_show(savemsg_combo);
6828 if (prefs_common.compose_save_to_history)
6829 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6830 prefs_common.compose_save_to_history);
6832 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6833 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6834 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6835 G_CALLBACK(compose_grab_focus_cb), compose);
6836 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6837 folderidentifier = folder_item_get_identifier(account_get_special_folder
6838 (compose->account, F_OUTBOX));
6839 compose_set_save_to(compose, folderidentifier);
6840 g_free(folderidentifier);
6843 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6844 gtk_widget_show(savemsg_select);
6845 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6846 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6847 G_CALLBACK(compose_savemsg_select_cb),
6855 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6857 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6858 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6861 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6866 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6869 path = folder_item_get_identifier(dest);
6871 compose_set_save_to(compose, path);
6875 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6876 GdkAtom clip, GtkTextIter *insert_place);
6879 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6883 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6885 if (event->button == 3) {
6887 GtkTextIter sel_start, sel_end;
6888 gboolean stuff_selected;
6890 /* move the cursor to allow GtkAspell to check the word
6891 * under the mouse */
6892 if (event->x && event->y) {
6893 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6894 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6896 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6899 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6900 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6903 stuff_selected = gtk_text_buffer_get_selection_bounds(
6905 &sel_start, &sel_end);
6907 gtk_text_buffer_place_cursor (buffer, &iter);
6908 /* reselect stuff */
6910 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6911 gtk_text_buffer_select_range(buffer,
6912 &sel_start, &sel_end);
6914 return FALSE; /* pass the event so that the right-click goes through */
6917 if (event->button == 2) {
6922 /* get the middle-click position to paste at the correct place */
6923 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6924 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6926 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6929 entry_paste_clipboard(compose, text,
6930 prefs_common.linewrap_pastes,
6931 GDK_SELECTION_PRIMARY, &iter);
6939 static void compose_spell_menu_changed(void *data)
6941 Compose *compose = (Compose *)data;
6943 GtkWidget *menuitem;
6944 GtkWidget *parent_item;
6945 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6948 if (compose->gtkaspell == NULL)
6951 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6952 "/Menu/Spelling/Options");
6954 /* setting the submenu removes /Spelling/Options from the factory
6955 * so we need to save it */
6957 if (parent_item == NULL) {
6958 parent_item = compose->aspell_options_menu;
6959 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6961 compose->aspell_options_menu = parent_item;
6963 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6965 spell_menu = g_slist_reverse(spell_menu);
6966 for (items = spell_menu;
6967 items; items = items->next) {
6968 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6969 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6970 gtk_widget_show(GTK_WIDGET(menuitem));
6972 g_slist_free(spell_menu);
6974 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6975 gtk_widget_show(parent_item);
6978 static void compose_dict_changed(void *data)
6980 Compose *compose = (Compose *) data;
6982 if(compose->gtkaspell &&
6983 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6986 gtkaspell_highlight_all(compose->gtkaspell);
6987 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6991 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6993 Compose *compose = (Compose *)data;
6994 GdkEventButton event;
6997 event.time = gtk_get_current_event_time();
7001 return text_clicked(compose->text, &event, compose);
7004 static gboolean compose_force_window_origin = TRUE;
7005 static Compose *compose_create(PrefsAccount *account,
7014 GtkWidget *handlebox;
7016 GtkWidget *notebook;
7018 GtkWidget *attach_hbox;
7019 GtkWidget *attach_lab1;
7020 GtkWidget *attach_lab2;
7025 GtkWidget *subject_hbox;
7026 GtkWidget *subject_frame;
7027 GtkWidget *subject_entry;
7031 GtkWidget *edit_vbox;
7032 GtkWidget *ruler_hbox;
7034 GtkWidget *scrolledwin;
7036 GtkTextBuffer *buffer;
7037 GtkClipboard *clipboard;
7040 UndoMain *undostruct;
7042 gchar *titles[N_ATTACH_COLS];
7043 GtkWidget *popupmenu;
7044 GtkWidget *tmpl_menu;
7045 GtkActionGroup *action_group = NULL;
7048 GtkAspell * gtkaspell = NULL;
7051 static GdkGeometry geometry;
7053 cm_return_val_if_fail(account != NULL, NULL);
7055 debug_print("Creating compose window...\n");
7056 compose = g_new0(Compose, 1);
7058 titles[COL_MIMETYPE] = _("MIME type");
7059 titles[COL_SIZE] = _("Size");
7060 titles[COL_NAME] = _("Name");
7062 compose->batch = batch;
7063 compose->account = account;
7064 compose->folder = folder;
7066 compose->mutex = g_mutex_new();
7067 compose->set_cursor_pos = -1;
7069 #if !(GTK_CHECK_VERSION(2,12,0))
7070 compose->tooltips = tips;
7073 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7075 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7076 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7078 if (!geometry.max_width) {
7079 geometry.max_width = gdk_screen_width();
7080 geometry.max_height = gdk_screen_height();
7083 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7084 &geometry, GDK_HINT_MAX_SIZE);
7085 if (!geometry.min_width) {
7086 geometry.min_width = 600;
7087 geometry.min_height = 440;
7089 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7090 &geometry, GDK_HINT_MIN_SIZE);
7092 #ifndef GENERIC_UMPC
7093 if (compose_force_window_origin)
7094 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7095 prefs_common.compose_y);
7097 g_signal_connect(G_OBJECT(window), "delete_event",
7098 G_CALLBACK(compose_delete_cb), compose);
7099 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7100 gtk_widget_realize(window);
7102 gtkut_widget_set_composer_icon(window);
7104 vbox = gtk_vbox_new(FALSE, 0);
7105 gtk_container_add(GTK_CONTAINER(window), vbox);
7107 compose->ui_manager = gtk_ui_manager_new();
7108 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7109 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7110 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7111 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7112 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7113 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7114 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7115 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7116 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7117 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7152 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7182 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7217 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7219 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7228 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7231 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7233 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7237 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)
7238 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)
7239 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7241 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7243 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7244 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)
7245 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)
7247 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7249 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7250 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)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7254 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)
7255 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7257 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7259 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7260 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)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7263 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7266 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)
7267 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)
7268 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7269 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7271 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7272 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7273 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7276 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7278 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7279 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7280 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)
7282 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7283 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7284 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7288 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7289 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7290 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7291 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7292 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7293 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7296 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7298 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7299 gtk_widget_show_all(menubar);
7301 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7303 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7305 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7308 if (prefs_common.toolbar_detachable) {
7309 handlebox = gtk_handle_box_new();
7311 handlebox = gtk_hbox_new(FALSE, 0);
7313 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7315 gtk_widget_realize(handlebox);
7317 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7320 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7324 vbox2 = gtk_vbox_new(FALSE, 2);
7325 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7326 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7329 notebook = gtk_notebook_new();
7330 gtk_widget_set_size_request(notebook, -1, 130);
7331 gtk_widget_show(notebook);
7333 /* header labels and entries */
7334 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7335 compose_create_header(compose),
7336 gtk_label_new_with_mnemonic(_("Hea_der")));
7337 /* attachment list */
7338 attach_hbox = gtk_hbox_new(FALSE, 0);
7339 gtk_widget_show(attach_hbox);
7341 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7342 gtk_widget_show(attach_lab1);
7343 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7345 attach_lab2 = gtk_label_new("");
7346 gtk_widget_show(attach_lab2);
7347 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7349 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7350 compose_create_attach(compose),
7353 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7354 compose_create_others(compose),
7355 gtk_label_new_with_mnemonic(_("Othe_rs")));
7358 subject_hbox = gtk_hbox_new(FALSE, 0);
7359 gtk_widget_show(subject_hbox);
7361 subject_frame = gtk_frame_new(NULL);
7362 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7363 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7364 gtk_widget_show(subject_frame);
7366 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7367 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7368 gtk_widget_show(subject);
7370 label = gtk_label_new(_("Subject:"));
7371 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7372 gtk_widget_show(label);
7375 subject_entry = claws_spell_entry_new();
7377 subject_entry = gtk_entry_new();
7379 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7380 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7381 G_CALLBACK(compose_grab_focus_cb), compose);
7382 gtk_widget_show(subject_entry);
7383 compose->subject_entry = subject_entry;
7384 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7386 edit_vbox = gtk_vbox_new(FALSE, 0);
7388 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7391 ruler_hbox = gtk_hbox_new(FALSE, 0);
7392 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7394 ruler = gtk_shruler_new();
7395 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7396 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7400 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7401 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7402 GTK_POLICY_AUTOMATIC,
7403 GTK_POLICY_AUTOMATIC);
7404 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7406 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7407 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7409 text = gtk_text_view_new();
7410 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7411 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7412 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7413 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7414 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7415 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7416 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7418 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7420 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7421 G_CALLBACK(compose_edit_size_alloc),
7423 g_signal_connect(G_OBJECT(buffer), "changed",
7424 G_CALLBACK(compose_changed_cb), compose);
7425 g_signal_connect(G_OBJECT(text), "grab_focus",
7426 G_CALLBACK(compose_grab_focus_cb), compose);
7427 g_signal_connect(G_OBJECT(buffer), "insert_text",
7428 G_CALLBACK(text_inserted), compose);
7429 g_signal_connect(G_OBJECT(text), "button_press_event",
7430 G_CALLBACK(text_clicked), compose);
7432 g_signal_connect(G_OBJECT(text), "popup-menu",
7433 G_CALLBACK(compose_popup_menu), compose);
7435 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7436 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7437 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7438 G_CALLBACK(compose_popup_menu), compose);
7440 g_signal_connect(G_OBJECT(subject_entry), "changed",
7441 G_CALLBACK(compose_changed_cb), compose);
7444 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7445 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7446 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7447 g_signal_connect(G_OBJECT(text), "drag_data_received",
7448 G_CALLBACK(compose_insert_drag_received_cb),
7450 g_signal_connect(G_OBJECT(text), "drag-drop",
7451 G_CALLBACK(compose_drag_drop),
7453 gtk_widget_show_all(vbox);
7455 /* pane between attach clist and text */
7456 paned = gtk_vpaned_new();
7457 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7459 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7460 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7462 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7464 gtk_paned_add1(GTK_PANED(paned), notebook);
7465 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7466 gtk_widget_show_all(paned);
7469 if (prefs_common.textfont) {
7470 PangoFontDescription *font_desc;
7472 font_desc = pango_font_description_from_string
7473 (prefs_common.textfont);
7475 gtk_widget_modify_font(text, font_desc);
7476 pango_font_description_free(font_desc);
7480 gtk_action_group_add_actions(action_group, compose_popup_entries,
7481 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7484 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7486 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7489 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7491 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7492 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7493 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7495 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7497 undostruct = undo_init(text);
7498 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7501 address_completion_start(window);
7503 compose->window = window;
7504 compose->vbox = vbox;
7505 compose->menubar = menubar;
7506 compose->handlebox = handlebox;
7508 compose->vbox2 = vbox2;
7510 compose->paned = paned;
7512 compose->attach_label = attach_lab2;
7514 compose->notebook = notebook;
7515 compose->edit_vbox = edit_vbox;
7516 compose->ruler_hbox = ruler_hbox;
7517 compose->ruler = ruler;
7518 compose->scrolledwin = scrolledwin;
7519 compose->text = text;
7521 compose->focused_editable = NULL;
7523 compose->popupmenu = popupmenu;
7525 compose->tmpl_menu = tmpl_menu;
7527 compose->mode = mode;
7528 compose->rmode = mode;
7530 compose->targetinfo = NULL;
7531 compose->replyinfo = NULL;
7532 compose->fwdinfo = NULL;
7534 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7535 g_str_equal, (GDestroyNotify) g_free, NULL);
7537 compose->replyto = NULL;
7539 compose->bcc = NULL;
7540 compose->followup_to = NULL;
7542 compose->ml_post = NULL;
7544 compose->inreplyto = NULL;
7545 compose->references = NULL;
7546 compose->msgid = NULL;
7547 compose->boundary = NULL;
7549 compose->autowrap = prefs_common.autowrap;
7550 compose->autoindent = prefs_common.auto_indent;
7551 compose->use_signing = FALSE;
7552 compose->use_encryption = FALSE;
7553 compose->privacy_system = NULL;
7555 compose->modified = FALSE;
7557 compose->return_receipt = FALSE;
7559 compose->to_list = NULL;
7560 compose->newsgroup_list = NULL;
7562 compose->undostruct = undostruct;
7564 compose->sig_str = NULL;
7566 compose->exteditor_file = NULL;
7567 compose->exteditor_pid = -1;
7568 compose->exteditor_tag = -1;
7569 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7572 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7573 if (mode != COMPOSE_REDIRECT) {
7574 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7575 strcmp(prefs_common.dictionary, "")) {
7576 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7577 prefs_common.alt_dictionary,
7578 conv_get_locale_charset_str(),
7579 prefs_common.misspelled_col,
7580 prefs_common.check_while_typing,
7581 prefs_common.recheck_when_changing_dict,
7582 prefs_common.use_alternate,
7583 prefs_common.use_both_dicts,
7584 GTK_TEXT_VIEW(text),
7585 GTK_WINDOW(compose->window),
7586 compose_dict_changed,
7587 compose_spell_menu_changed,
7590 alertpanel_error(_("Spell checker could not "
7592 gtkaspell_checkers_strerror());
7593 gtkaspell_checkers_reset_error();
7595 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7599 compose->gtkaspell = gtkaspell;
7600 compose_spell_menu_changed(compose);
7601 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7604 compose_select_account(compose, account, TRUE);
7606 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7607 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7609 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7610 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7612 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7613 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7615 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7616 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7618 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7619 if (account->protocol != A_NNTP)
7620 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7621 prefs_common_translated_header_name("To:"));
7623 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7624 prefs_common_translated_header_name("Newsgroups:"));
7626 addressbook_set_target_compose(compose);
7628 if (mode != COMPOSE_REDIRECT)
7629 compose_set_template_menu(compose);
7631 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7634 compose_list = g_list_append(compose_list, compose);
7636 if (!prefs_common.show_ruler)
7637 gtk_widget_hide(ruler_hbox);
7639 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7642 compose->priority = PRIORITY_NORMAL;
7643 compose_update_priority_menu_item(compose);
7645 compose_set_out_encoding(compose);
7648 compose_update_actions_menu(compose);
7650 /* Privacy Systems menu */
7651 compose_update_privacy_systems_menu(compose);
7653 activate_privacy_system(compose, account, TRUE);
7654 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7656 gtk_widget_realize(window);
7658 gtk_widget_show(window);
7660 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7661 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7668 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7673 GtkWidget *optmenubox;
7676 GtkWidget *from_name = NULL;
7677 #if !(GTK_CHECK_VERSION(2,12,0))
7678 GtkTooltips *tips = compose->tooltips;
7681 gint num = 0, def_menu = 0;
7683 accounts = account_get_list();
7684 cm_return_val_if_fail(accounts != NULL, NULL);
7686 optmenubox = gtk_event_box_new();
7687 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7688 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7690 hbox = gtk_hbox_new(FALSE, 6);
7691 from_name = gtk_entry_new();
7693 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7694 G_CALLBACK(compose_grab_focus_cb), compose);
7696 for (; accounts != NULL; accounts = accounts->next, num++) {
7697 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7698 gchar *name, *from = NULL;
7700 if (ac == compose->account) def_menu = num;
7702 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7705 if (ac == compose->account) {
7706 if (ac->name && *ac->name) {
7708 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7709 from = g_strdup_printf("%s <%s>",
7711 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7713 from = g_strdup_printf("%s",
7715 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7718 COMBOBOX_ADD(menu, name, ac->account_id);
7723 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7725 g_signal_connect(G_OBJECT(optmenu), "changed",
7726 G_CALLBACK(account_activated),
7728 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7729 G_CALLBACK(compose_entry_popup_extend),
7732 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7733 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7735 CLAWS_SET_TIP(optmenubox,
7736 _("Account to use for this email"));
7737 CLAWS_SET_TIP(from_name,
7738 _("Sender address to be used"));
7740 compose->account_combo = optmenu;
7741 compose->from_name = from_name;
7746 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7748 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7749 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7750 Compose *compose = (Compose *) data;
7752 compose->priority = value;
7756 static void compose_reply_change_mode(Compose *compose,
7759 gboolean was_modified = compose->modified;
7761 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7763 cm_return_if_fail(compose->replyinfo != NULL);
7765 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7767 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7769 if (action == COMPOSE_REPLY_TO_ALL)
7771 if (action == COMPOSE_REPLY_TO_SENDER)
7773 if (action == COMPOSE_REPLY_TO_LIST)
7776 compose_remove_header_entries(compose);
7777 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7778 if (compose->account->set_autocc && compose->account->auto_cc)
7779 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7781 if (compose->account->set_autobcc && compose->account->auto_bcc)
7782 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7784 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7785 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7786 compose_show_first_last_header(compose, TRUE);
7787 compose->modified = was_modified;
7788 compose_set_title(compose);
7791 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7793 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7794 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7795 Compose *compose = (Compose *) data;
7798 compose_reply_change_mode(compose, value);
7801 static void compose_update_priority_menu_item(Compose * compose)
7803 GtkWidget *menuitem = NULL;
7804 switch (compose->priority) {
7805 case PRIORITY_HIGHEST:
7806 menuitem = gtk_ui_manager_get_widget
7807 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7810 menuitem = gtk_ui_manager_get_widget
7811 (compose->ui_manager, "/Menu/Options/Priority/High");
7813 case PRIORITY_NORMAL:
7814 menuitem = gtk_ui_manager_get_widget
7815 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7818 menuitem = gtk_ui_manager_get_widget
7819 (compose->ui_manager, "/Menu/Options/Priority/Low");
7821 case PRIORITY_LOWEST:
7822 menuitem = gtk_ui_manager_get_widget
7823 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7826 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7829 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7831 Compose *compose = (Compose *) data;
7833 gboolean can_sign = FALSE, can_encrypt = FALSE;
7835 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7837 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7840 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7841 g_free(compose->privacy_system);
7842 compose->privacy_system = NULL;
7843 if (systemid != NULL) {
7844 compose->privacy_system = g_strdup(systemid);
7846 can_sign = privacy_system_can_sign(systemid);
7847 can_encrypt = privacy_system_can_encrypt(systemid);
7850 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7852 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7853 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7856 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7858 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7859 GtkWidget *menuitem = NULL;
7861 gboolean can_sign = FALSE, can_encrypt = FALSE;
7862 gboolean found = FALSE;
7864 if (compose->privacy_system != NULL) {
7866 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7867 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7868 cm_return_if_fail(menuitem != NULL);
7870 amenu = GTK_MENU_SHELL(menuitem)->children;
7872 while (amenu != NULL) {
7873 GList *alist = amenu->next;
7875 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7876 if (systemid != NULL) {
7877 if (strcmp(systemid, compose->privacy_system) == 0 &&
7878 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7879 menuitem = GTK_WIDGET(amenu->data);
7881 can_sign = privacy_system_can_sign(systemid);
7882 can_encrypt = privacy_system_can_encrypt(systemid);
7886 } else if (strlen(compose->privacy_system) == 0 &&
7887 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7888 menuitem = GTK_WIDGET(amenu->data);
7891 can_encrypt = FALSE;
7898 if (menuitem != NULL)
7899 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7901 if (warn && !found && strlen(compose->privacy_system)) {
7902 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7903 "will not be able to sign or encrypt this message."),
7904 compose->privacy_system);
7908 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7909 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7912 static void compose_set_out_encoding(Compose *compose)
7914 CharSet out_encoding;
7915 const gchar *branch = NULL;
7916 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7918 switch(out_encoding) {
7919 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7920 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7921 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7922 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7923 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7924 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7925 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7926 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7927 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7928 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7929 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7930 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7931 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7932 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7933 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7934 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7935 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7936 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7937 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7938 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7939 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7940 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7941 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7942 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
7943 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7944 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7945 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7946 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7947 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7948 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7949 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7950 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7951 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7953 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7956 static void compose_set_template_menu(Compose *compose)
7958 GSList *tmpl_list, *cur;
7962 tmpl_list = template_get_config();
7964 menu = gtk_menu_new();
7966 gtk_menu_set_accel_group (GTK_MENU (menu),
7967 gtk_ui_manager_get_accel_group(compose->ui_manager));
7968 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7969 Template *tmpl = (Template *)cur->data;
7970 gchar *accel_path = NULL;
7971 item = gtk_menu_item_new_with_label(tmpl->name);
7972 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7973 g_signal_connect(G_OBJECT(item), "activate",
7974 G_CALLBACK(compose_template_activate_cb),
7976 g_object_set_data(G_OBJECT(item), "template", tmpl);
7977 gtk_widget_show(item);
7978 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7979 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7983 gtk_widget_show(menu);
7984 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7987 void compose_update_actions_menu(Compose *compose)
7989 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7992 static void compose_update_privacy_systems_menu(Compose *compose)
7994 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7995 GSList *systems, *cur;
7997 GtkWidget *system_none;
7999 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8000 GtkWidget *privacy_menu = gtk_menu_new();
8002 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8003 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8005 g_signal_connect(G_OBJECT(system_none), "activate",
8006 G_CALLBACK(compose_set_privacy_system_cb), compose);
8008 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8009 gtk_widget_show(system_none);
8011 systems = privacy_get_system_ids();
8012 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8013 gchar *systemid = cur->data;
8015 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8016 widget = gtk_radio_menu_item_new_with_label(group,
8017 privacy_system_get_name(systemid));
8018 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8019 g_strdup(systemid), g_free);
8020 g_signal_connect(G_OBJECT(widget), "activate",
8021 G_CALLBACK(compose_set_privacy_system_cb), compose);
8023 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8024 gtk_widget_show(widget);
8027 g_slist_free(systems);
8028 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8029 gtk_widget_show_all(privacy_menu);
8030 gtk_widget_show_all(privacy_menuitem);
8033 void compose_reflect_prefs_all(void)
8038 for (cur = compose_list; cur != NULL; cur = cur->next) {
8039 compose = (Compose *)cur->data;
8040 compose_set_template_menu(compose);
8044 void compose_reflect_prefs_pixmap_theme(void)
8049 for (cur = compose_list; cur != NULL; cur = cur->next) {
8050 compose = (Compose *)cur->data;
8051 toolbar_update(TOOLBAR_COMPOSE, compose);
8055 static const gchar *compose_quote_char_from_context(Compose *compose)
8057 const gchar *qmark = NULL;
8059 cm_return_val_if_fail(compose != NULL, NULL);
8061 switch (compose->mode) {
8062 /* use forward-specific quote char */
8063 case COMPOSE_FORWARD:
8064 case COMPOSE_FORWARD_AS_ATTACH:
8065 case COMPOSE_FORWARD_INLINE:
8066 if (compose->folder && compose->folder->prefs &&
8067 compose->folder->prefs->forward_with_format)
8068 qmark = compose->folder->prefs->forward_quotemark;
8069 else if (compose->account->forward_with_format)
8070 qmark = compose->account->forward_quotemark;
8072 qmark = prefs_common.fw_quotemark;
8075 /* use reply-specific quote char in all other modes */
8077 if (compose->folder && compose->folder->prefs &&
8078 compose->folder->prefs->reply_with_format)
8079 qmark = compose->folder->prefs->reply_quotemark;
8080 else if (compose->account->reply_with_format)
8081 qmark = compose->account->reply_quotemark;
8083 qmark = prefs_common.quotemark;
8087 if (qmark == NULL || *qmark == '\0')
8093 static void compose_template_apply(Compose *compose, Template *tmpl,
8097 GtkTextBuffer *buffer;
8101 gchar *parsed_str = NULL;
8102 gint cursor_pos = 0;
8103 const gchar *err_msg = _("The body of the template has an error at line %d.");
8106 /* process the body */
8108 text = GTK_TEXT_VIEW(compose->text);
8109 buffer = gtk_text_view_get_buffer(text);
8112 qmark = compose_quote_char_from_context(compose);
8114 if (compose->replyinfo != NULL) {
8117 gtk_text_buffer_set_text(buffer, "", -1);
8118 mark = gtk_text_buffer_get_insert(buffer);
8119 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8121 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8122 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8124 } else if (compose->fwdinfo != NULL) {
8127 gtk_text_buffer_set_text(buffer, "", -1);
8128 mark = gtk_text_buffer_get_insert(buffer);
8129 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8131 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8132 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8135 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8137 GtkTextIter start, end;
8140 gtk_text_buffer_get_start_iter(buffer, &start);
8141 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8142 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8144 /* clear the buffer now */
8146 gtk_text_buffer_set_text(buffer, "", -1);
8148 parsed_str = compose_quote_fmt(compose, dummyinfo,
8149 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8150 procmsg_msginfo_free( dummyinfo );
8156 gtk_text_buffer_set_text(buffer, "", -1);
8157 mark = gtk_text_buffer_get_insert(buffer);
8158 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8161 if (replace && parsed_str && compose->account->auto_sig)
8162 compose_insert_sig(compose, FALSE);
8164 if (replace && parsed_str) {
8165 gtk_text_buffer_get_start_iter(buffer, &iter);
8166 gtk_text_buffer_place_cursor(buffer, &iter);
8170 cursor_pos = quote_fmt_get_cursor_pos();
8171 compose->set_cursor_pos = cursor_pos;
8172 if (cursor_pos == -1)
8174 gtk_text_buffer_get_start_iter(buffer, &iter);
8175 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8176 gtk_text_buffer_place_cursor(buffer, &iter);
8179 /* process the other fields */
8181 compose_template_apply_fields(compose, tmpl);
8182 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8183 quote_fmt_reset_vartable();
8184 compose_changed_cb(NULL, compose);
8187 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8188 gtkaspell_highlight_all(compose->gtkaspell);
8192 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8194 MsgInfo* dummyinfo = NULL;
8195 MsgInfo *msginfo = NULL;
8198 if (compose->replyinfo != NULL)
8199 msginfo = compose->replyinfo;
8200 else if (compose->fwdinfo != NULL)
8201 msginfo = compose->fwdinfo;
8203 dummyinfo = compose_msginfo_new_from_compose(compose);
8204 msginfo = dummyinfo;
8207 if (tmpl->from && *tmpl->from != '\0') {
8209 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8210 compose->gtkaspell);
8212 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8214 quote_fmt_scan_string(tmpl->from);
8217 buf = quote_fmt_get_buffer();
8219 alertpanel_error(_("Template From format error."));
8221 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8225 if (tmpl->to && *tmpl->to != '\0') {
8227 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8228 compose->gtkaspell);
8230 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8232 quote_fmt_scan_string(tmpl->to);
8235 buf = quote_fmt_get_buffer();
8237 alertpanel_error(_("Template To format error."));
8239 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8243 if (tmpl->cc && *tmpl->cc != '\0') {
8245 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8246 compose->gtkaspell);
8248 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8250 quote_fmt_scan_string(tmpl->cc);
8253 buf = quote_fmt_get_buffer();
8255 alertpanel_error(_("Template Cc format error."));
8257 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8261 if (tmpl->bcc && *tmpl->bcc != '\0') {
8263 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8264 compose->gtkaspell);
8266 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8268 quote_fmt_scan_string(tmpl->bcc);
8271 buf = quote_fmt_get_buffer();
8273 alertpanel_error(_("Template Bcc format error."));
8275 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8279 /* process the subject */
8280 if (tmpl->subject && *tmpl->subject != '\0') {
8282 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8283 compose->gtkaspell);
8285 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8287 quote_fmt_scan_string(tmpl->subject);
8290 buf = quote_fmt_get_buffer();
8292 alertpanel_error(_("Template subject format error."));
8294 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8298 procmsg_msginfo_free( dummyinfo );
8301 static void compose_destroy(Compose *compose)
8303 GtkTextBuffer *buffer;
8304 GtkClipboard *clipboard;
8306 compose_list = g_list_remove(compose_list, compose);
8308 if (compose->updating) {
8309 debug_print("danger, not destroying anything now\n");
8310 compose->deferred_destroy = TRUE;
8313 /* NOTE: address_completion_end() does nothing with the window
8314 * however this may change. */
8315 address_completion_end(compose->window);
8317 slist_free_strings(compose->to_list);
8318 g_slist_free(compose->to_list);
8319 slist_free_strings(compose->newsgroup_list);
8320 g_slist_free(compose->newsgroup_list);
8321 slist_free_strings(compose->header_list);
8322 g_slist_free(compose->header_list);
8324 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8326 g_hash_table_destroy(compose->email_hashtable);
8328 procmsg_msginfo_free(compose->targetinfo);
8329 procmsg_msginfo_free(compose->replyinfo);
8330 procmsg_msginfo_free(compose->fwdinfo);
8332 g_free(compose->replyto);
8333 g_free(compose->cc);
8334 g_free(compose->bcc);
8335 g_free(compose->newsgroups);
8336 g_free(compose->followup_to);
8338 g_free(compose->ml_post);
8340 g_free(compose->inreplyto);
8341 g_free(compose->references);
8342 g_free(compose->msgid);
8343 g_free(compose->boundary);
8345 g_free(compose->redirect_filename);
8346 if (compose->undostruct)
8347 undo_destroy(compose->undostruct);
8349 g_free(compose->sig_str);
8351 g_free(compose->exteditor_file);
8353 g_free(compose->orig_charset);
8355 g_free(compose->privacy_system);
8357 if (addressbook_get_target_compose() == compose)
8358 addressbook_set_target_compose(NULL);
8361 if (compose->gtkaspell) {
8362 gtkaspell_delete(compose->gtkaspell);
8363 compose->gtkaspell = NULL;
8367 if (!compose->batch) {
8368 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8369 prefs_common.compose_height = compose->window->allocation.height;
8372 if (!gtk_widget_get_parent(compose->paned))
8373 gtk_widget_destroy(compose->paned);
8374 gtk_widget_destroy(compose->popupmenu);
8376 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8377 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8378 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8380 gtk_widget_destroy(compose->window);
8381 toolbar_destroy(compose->toolbar);
8382 g_free(compose->toolbar);
8383 g_mutex_free(compose->mutex);
8387 static void compose_attach_info_free(AttachInfo *ainfo)
8389 g_free(ainfo->file);
8390 g_free(ainfo->content_type);
8391 g_free(ainfo->name);
8395 static void compose_attach_update_label(Compose *compose)
8400 GtkTreeModel *model;
8405 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8406 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8407 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8411 while(gtk_tree_model_iter_next(model, &iter))
8414 text = g_strdup_printf("(%d)", i);
8415 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8419 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8421 Compose *compose = (Compose *)data;
8422 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8423 GtkTreeSelection *selection;
8425 GtkTreeModel *model;
8427 selection = gtk_tree_view_get_selection(tree_view);
8428 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8433 for (cur = sel; cur != NULL; cur = cur->next) {
8434 GtkTreePath *path = cur->data;
8435 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8438 gtk_tree_path_free(path);
8441 for (cur = sel; cur != NULL; cur = cur->next) {
8442 GtkTreeRowReference *ref = cur->data;
8443 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8446 if (gtk_tree_model_get_iter(model, &iter, path))
8447 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8449 gtk_tree_path_free(path);
8450 gtk_tree_row_reference_free(ref);
8454 compose_attach_update_label(compose);
8457 static struct _AttachProperty
8460 GtkWidget *mimetype_entry;
8461 GtkWidget *encoding_optmenu;
8462 GtkWidget *path_entry;
8463 GtkWidget *filename_entry;
8465 GtkWidget *cancel_btn;
8468 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8470 gtk_tree_path_free((GtkTreePath *)ptr);
8473 static void compose_attach_property(GtkAction *action, gpointer data)
8475 Compose *compose = (Compose *)data;
8476 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8478 GtkComboBox *optmenu;
8479 GtkTreeSelection *selection;
8481 GtkTreeModel *model;
8484 static gboolean cancelled;
8486 /* only if one selected */
8487 selection = gtk_tree_view_get_selection(tree_view);
8488 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8491 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8495 path = (GtkTreePath *) sel->data;
8496 gtk_tree_model_get_iter(model, &iter, path);
8497 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8500 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8506 if (!attach_prop.window)
8507 compose_attach_property_create(&cancelled);
8508 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8509 gtk_widget_grab_focus(attach_prop.ok_btn);
8510 gtk_widget_show(attach_prop.window);
8511 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8513 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8514 if (ainfo->encoding == ENC_UNKNOWN)
8515 combobox_select_by_data(optmenu, ENC_BASE64);
8517 combobox_select_by_data(optmenu, ainfo->encoding);
8519 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8520 ainfo->content_type ? ainfo->content_type : "");
8521 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8522 ainfo->file ? ainfo->file : "");
8523 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8524 ainfo->name ? ainfo->name : "");
8527 const gchar *entry_text;
8529 gchar *cnttype = NULL;
8536 gtk_widget_hide(attach_prop.window);
8537 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8542 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8543 if (*entry_text != '\0') {
8546 text = g_strstrip(g_strdup(entry_text));
8547 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8548 cnttype = g_strdup(text);
8551 alertpanel_error(_("Invalid MIME type."));
8557 ainfo->encoding = combobox_get_active_data(optmenu);
8559 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8560 if (*entry_text != '\0') {
8561 if (is_file_exist(entry_text) &&
8562 (size = get_file_size(entry_text)) > 0)
8563 file = g_strdup(entry_text);
8566 (_("File doesn't exist or is empty."));
8572 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8573 if (*entry_text != '\0') {
8574 g_free(ainfo->name);
8575 ainfo->name = g_strdup(entry_text);
8579 g_free(ainfo->content_type);
8580 ainfo->content_type = cnttype;
8583 g_free(ainfo->file);
8587 ainfo->size = (goffset)size;
8589 /* update tree store */
8590 text = to_human_readable(ainfo->size);
8591 gtk_tree_model_get_iter(model, &iter, path);
8592 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8593 COL_MIMETYPE, ainfo->content_type,
8595 COL_NAME, ainfo->name,
8601 gtk_tree_path_free(path);
8604 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8606 label = gtk_label_new(str); \
8607 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8608 GTK_FILL, 0, 0, 0); \
8609 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8611 entry = gtk_entry_new(); \
8612 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8613 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8616 static void compose_attach_property_create(gboolean *cancelled)
8622 GtkWidget *mimetype_entry;
8625 GtkListStore *optmenu_menu;
8626 GtkWidget *path_entry;
8627 GtkWidget *filename_entry;
8630 GtkWidget *cancel_btn;
8631 GList *mime_type_list, *strlist;
8634 debug_print("Creating attach_property window...\n");
8636 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8637 gtk_widget_set_size_request(window, 480, -1);
8638 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8639 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8640 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8641 g_signal_connect(G_OBJECT(window), "delete_event",
8642 G_CALLBACK(attach_property_delete_event),
8644 g_signal_connect(G_OBJECT(window), "key_press_event",
8645 G_CALLBACK(attach_property_key_pressed),
8648 vbox = gtk_vbox_new(FALSE, 8);
8649 gtk_container_add(GTK_CONTAINER(window), vbox);
8651 table = gtk_table_new(4, 2, FALSE);
8652 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8653 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8654 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8656 label = gtk_label_new(_("MIME type"));
8657 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8659 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8660 mimetype_entry = gtk_combo_box_entry_new_text();
8661 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8662 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8664 /* stuff with list */
8665 mime_type_list = procmime_get_mime_type_list();
8667 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8668 MimeType *type = (MimeType *) mime_type_list->data;
8671 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8673 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8676 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8677 (GCompareFunc)strcmp2);
8680 for (mime_type_list = strlist; mime_type_list != NULL;
8681 mime_type_list = mime_type_list->next) {
8682 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8683 g_free(mime_type_list->data);
8685 g_list_free(strlist);
8686 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8687 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8689 label = gtk_label_new(_("Encoding"));
8690 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8692 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8694 hbox = gtk_hbox_new(FALSE, 0);
8695 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8696 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8698 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8699 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8701 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8702 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8703 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8704 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8705 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8707 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8709 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8710 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8712 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8713 &ok_btn, GTK_STOCK_OK,
8715 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8716 gtk_widget_grab_default(ok_btn);
8718 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8719 G_CALLBACK(attach_property_ok),
8721 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8722 G_CALLBACK(attach_property_cancel),
8725 gtk_widget_show_all(vbox);
8727 attach_prop.window = window;
8728 attach_prop.mimetype_entry = mimetype_entry;
8729 attach_prop.encoding_optmenu = optmenu;
8730 attach_prop.path_entry = path_entry;
8731 attach_prop.filename_entry = filename_entry;
8732 attach_prop.ok_btn = ok_btn;
8733 attach_prop.cancel_btn = cancel_btn;
8736 #undef SET_LABEL_AND_ENTRY
8738 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8744 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8750 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8751 gboolean *cancelled)
8759 static gboolean attach_property_key_pressed(GtkWidget *widget,
8761 gboolean *cancelled)
8763 if (event && event->keyval == GDK_Escape) {
8767 if (event && event->keyval == GDK_Return) {
8775 static void compose_exec_ext_editor(Compose *compose)
8782 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8783 G_DIR_SEPARATOR, compose);
8785 if (pipe(pipe_fds) < 0) {
8791 if ((pid = fork()) < 0) {
8798 /* close the write side of the pipe */
8801 compose->exteditor_file = g_strdup(tmp);
8802 compose->exteditor_pid = pid;
8804 compose_set_ext_editor_sensitive(compose, FALSE);
8807 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8809 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8811 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8815 } else { /* process-monitoring process */
8821 /* close the read side of the pipe */
8824 if (compose_write_body_to_file(compose, tmp) < 0) {
8825 fd_write_all(pipe_fds[1], "2\n", 2);
8829 pid_ed = compose_exec_ext_editor_real(tmp);
8831 fd_write_all(pipe_fds[1], "1\n", 2);
8835 /* wait until editor is terminated */
8836 waitpid(pid_ed, NULL, 0);
8838 fd_write_all(pipe_fds[1], "0\n", 2);
8845 #endif /* G_OS_UNIX */
8849 static gint compose_exec_ext_editor_real(const gchar *file)
8856 cm_return_val_if_fail(file != NULL, -1);
8858 if ((pid = fork()) < 0) {
8863 if (pid != 0) return pid;
8865 /* grandchild process */
8867 if (setpgid(0, getppid()))
8870 if (prefs_common_get_ext_editor_cmd() &&
8871 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8872 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8873 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8875 if (prefs_common_get_ext_editor_cmd())
8876 g_warning("External editor command-line is invalid: '%s'\n",
8877 prefs_common_get_ext_editor_cmd());
8878 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8881 cmdline = strsplit_with_quote(buf, " ", 1024);
8882 execvp(cmdline[0], cmdline);
8885 g_strfreev(cmdline);
8890 static gboolean compose_ext_editor_kill(Compose *compose)
8892 pid_t pgid = compose->exteditor_pid * -1;
8895 ret = kill(pgid, 0);
8897 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8901 msg = g_strdup_printf
8902 (_("The external editor is still working.\n"
8903 "Force terminating the process?\n"
8904 "process group id: %d"), -pgid);
8905 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8906 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8910 if (val == G_ALERTALTERNATE) {
8911 g_source_remove(compose->exteditor_tag);
8912 g_io_channel_shutdown(compose->exteditor_ch,
8914 g_io_channel_unref(compose->exteditor_ch);
8916 if (kill(pgid, SIGTERM) < 0) perror("kill");
8917 waitpid(compose->exteditor_pid, NULL, 0);
8919 g_warning("Terminated process group id: %d", -pgid);
8920 g_warning("Temporary file: %s",
8921 compose->exteditor_file);
8923 compose_set_ext_editor_sensitive(compose, TRUE);
8925 g_free(compose->exteditor_file);
8926 compose->exteditor_file = NULL;
8927 compose->exteditor_pid = -1;
8928 compose->exteditor_ch = NULL;
8929 compose->exteditor_tag = -1;
8937 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8941 Compose *compose = (Compose *)data;
8944 debug_print(_("Compose: input from monitoring process\n"));
8946 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8948 g_io_channel_shutdown(source, FALSE, NULL);
8949 g_io_channel_unref(source);
8951 waitpid(compose->exteditor_pid, NULL, 0);
8953 if (buf[0] == '0') { /* success */
8954 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8955 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8957 gtk_text_buffer_set_text(buffer, "", -1);
8958 compose_insert_file(compose, compose->exteditor_file);
8959 compose_changed_cb(NULL, compose);
8960 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8962 if (claws_unlink(compose->exteditor_file) < 0)
8963 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8964 } else if (buf[0] == '1') { /* failed */
8965 g_warning("Couldn't exec external editor\n");
8966 if (claws_unlink(compose->exteditor_file) < 0)
8967 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8968 } else if (buf[0] == '2') {
8969 g_warning("Couldn't write to file\n");
8970 } else if (buf[0] == '3') {
8971 g_warning("Pipe read failed\n");
8974 compose_set_ext_editor_sensitive(compose, TRUE);
8976 g_free(compose->exteditor_file);
8977 compose->exteditor_file = NULL;
8978 compose->exteditor_pid = -1;
8979 compose->exteditor_ch = NULL;
8980 compose->exteditor_tag = -1;
8985 static void compose_set_ext_editor_sensitive(Compose *compose,
8988 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8990 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8991 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8994 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8996 gtk_widget_set_sensitive(compose->text, sensitive);
8997 if (compose->toolbar->send_btn)
8998 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8999 if (compose->toolbar->sendl_btn)
9000 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9001 if (compose->toolbar->draft_btn)
9002 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9003 if (compose->toolbar->insert_btn)
9004 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9005 if (compose->toolbar->sig_btn)
9006 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9007 if (compose->toolbar->exteditor_btn)
9008 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9009 if (compose->toolbar->linewrap_current_btn)
9010 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9011 if (compose->toolbar->linewrap_all_btn)
9012 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9014 #endif /* G_OS_UNIX */
9017 * compose_undo_state_changed:
9019 * Change the sensivity of the menuentries undo and redo
9021 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9022 gint redo_state, gpointer data)
9024 Compose *compose = (Compose *)data;
9026 switch (undo_state) {
9027 case UNDO_STATE_TRUE:
9028 if (!undostruct->undo_state) {
9029 undostruct->undo_state = TRUE;
9030 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9033 case UNDO_STATE_FALSE:
9034 if (undostruct->undo_state) {
9035 undostruct->undo_state = FALSE;
9036 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9039 case UNDO_STATE_UNCHANGED:
9041 case UNDO_STATE_REFRESH:
9042 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9045 g_warning("Undo state not recognized");
9049 switch (redo_state) {
9050 case UNDO_STATE_TRUE:
9051 if (!undostruct->redo_state) {
9052 undostruct->redo_state = TRUE;
9053 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9056 case UNDO_STATE_FALSE:
9057 if (undostruct->redo_state) {
9058 undostruct->redo_state = FALSE;
9059 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9062 case UNDO_STATE_UNCHANGED:
9064 case UNDO_STATE_REFRESH:
9065 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9068 g_warning("Redo state not recognized");
9073 /* callback functions */
9075 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9076 * includes "non-client" (windows-izm) in calculation, so this calculation
9077 * may not be accurate.
9079 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9080 GtkAllocation *allocation,
9081 GtkSHRuler *shruler)
9083 if (prefs_common.show_ruler) {
9084 gint char_width = 0, char_height = 0;
9085 gint line_width_in_chars;
9087 gtkut_get_font_size(GTK_WIDGET(widget),
9088 &char_width, &char_height);
9089 line_width_in_chars =
9090 (allocation->width - allocation->x) / char_width;
9092 /* got the maximum */
9093 gtk_ruler_set_range(GTK_RULER(shruler),
9094 0.0, line_width_in_chars, 0,
9095 /*line_width_in_chars*/ char_width);
9104 ComposePrefType type;
9105 gboolean entry_marked;
9108 static void account_activated(GtkComboBox *optmenu, gpointer data)
9110 Compose *compose = (Compose *)data;
9113 gchar *folderidentifier;
9114 gint account_id = 0;
9117 GSList *list, *saved_list = NULL;
9118 HeaderEntryState *state;
9119 GtkRcStyle *style = NULL;
9120 static GdkColor yellow;
9121 static gboolean color_set = FALSE;
9123 /* Get ID of active account in the combo box */
9124 menu = gtk_combo_box_get_model(optmenu);
9125 gtk_combo_box_get_active_iter(optmenu, &iter);
9126 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9128 ac = account_find_from_id(account_id);
9129 cm_return_if_fail(ac != NULL);
9131 if (ac != compose->account) {
9132 compose_select_account(compose, ac, FALSE);
9134 for (list = compose->header_list; list; list = list->next) {
9135 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9137 if (hentry->type == PREF_ACCOUNT || !list->next) {
9138 compose_destroy_headerentry(compose, hentry);
9142 state = g_malloc0(sizeof(HeaderEntryState));
9143 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9144 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9145 state->entry = gtk_editable_get_chars(
9146 GTK_EDITABLE(hentry->entry), 0, -1);
9147 state->type = hentry->type;
9150 gdk_color_parse("#f5f6be", &yellow);
9151 color_set = gdk_colormap_alloc_color(
9152 gdk_colormap_get_system(),
9153 &yellow, FALSE, TRUE);
9156 style = gtk_widget_get_modifier_style(hentry->entry);
9157 state->entry_marked = gdk_color_equal(&yellow,
9158 &style->base[GTK_STATE_NORMAL]);
9160 saved_list = g_slist_append(saved_list, state);
9161 compose_destroy_headerentry(compose, hentry);
9164 compose->header_last = NULL;
9165 g_slist_free(compose->header_list);
9166 compose->header_list = NULL;
9167 compose->header_nextrow = 1;
9168 compose_create_header_entry(compose);
9170 if (ac->set_autocc && ac->auto_cc)
9171 compose_entry_append(compose, ac->auto_cc,
9172 COMPOSE_CC, PREF_ACCOUNT);
9174 if (ac->set_autobcc && ac->auto_bcc)
9175 compose_entry_append(compose, ac->auto_bcc,
9176 COMPOSE_BCC, PREF_ACCOUNT);
9178 if (ac->set_autoreplyto && ac->auto_replyto)
9179 compose_entry_append(compose, ac->auto_replyto,
9180 COMPOSE_REPLYTO, PREF_ACCOUNT);
9182 for (list = saved_list; list; list = list->next) {
9183 state = (HeaderEntryState *) list->data;
9185 compose_add_header_entry(compose, state->header,
9186 state->entry, state->type);
9187 if (state->entry_marked)
9188 compose_entry_mark_default_to(compose, state->entry);
9190 g_free(state->header);
9191 g_free(state->entry);
9194 g_slist_free(saved_list);
9196 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9197 (ac->protocol == A_NNTP) ?
9198 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9201 /* Set message save folder */
9202 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9203 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9205 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9206 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9208 compose_set_save_to(compose, NULL);
9209 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9210 folderidentifier = folder_item_get_identifier(account_get_special_folder
9211 (compose->account, F_OUTBOX));
9212 compose_set_save_to(compose, folderidentifier);
9213 g_free(folderidentifier);
9217 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9218 GtkTreeViewColumn *column, Compose *compose)
9220 compose_attach_property(NULL, compose);
9223 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9226 Compose *compose = (Compose *)data;
9227 GtkTreeSelection *attach_selection;
9228 gint attach_nr_selected;
9230 if (!event) return FALSE;
9232 if (event->button == 3) {
9233 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9234 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9236 if (attach_nr_selected > 0)
9238 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9239 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9241 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9242 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9245 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9246 NULL, NULL, event->button, event->time);
9253 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9256 Compose *compose = (Compose *)data;
9258 if (!event) return FALSE;
9260 switch (event->keyval) {
9262 compose_attach_remove_selected(NULL, compose);
9268 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9270 toolbar_comp_set_sensitive(compose, allow);
9271 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9272 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9274 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9276 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9277 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9278 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9280 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9284 static void compose_send_cb(GtkAction *action, gpointer data)
9286 Compose *compose = (Compose *)data;
9288 if (prefs_common.work_offline &&
9289 !inc_offline_should_override(TRUE,
9290 _("Claws Mail needs network access in order "
9291 "to send this email.")))
9294 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9295 g_source_remove(compose->draft_timeout_tag);
9296 compose->draft_timeout_tag = -1;
9299 compose_send(compose);
9302 static void compose_send_later_cb(GtkAction *action, gpointer data)
9304 Compose *compose = (Compose *)data;
9308 compose_allow_user_actions(compose, FALSE);
9309 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9310 compose_allow_user_actions(compose, TRUE);
9314 compose_close(compose);
9315 } else if (val == -1) {
9316 alertpanel_error(_("Could not queue message."));
9317 } else if (val == -2) {
9318 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9319 } else if (val == -3) {
9320 if (privacy_peek_error())
9321 alertpanel_error(_("Could not queue message for sending:\n\n"
9322 "Signature failed: %s"), privacy_get_error());
9323 } else if (val == -4) {
9324 alertpanel_error(_("Could not queue message for sending:\n\n"
9325 "Charset conversion failed."));
9326 } else if (val == -5) {
9327 alertpanel_error(_("Could not queue message for sending:\n\n"
9328 "Couldn't get recipient encryption key."));
9329 } else if (val == -6) {
9332 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9335 #define DRAFTED_AT_EXIT "drafted_at_exit"
9336 static void compose_register_draft(MsgInfo *info)
9338 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9339 DRAFTED_AT_EXIT, NULL);
9340 FILE *fp = g_fopen(filepath, "ab");
9343 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9351 gboolean compose_draft (gpointer data, guint action)
9353 Compose *compose = (Compose *)data;
9357 MsgFlags flag = {0, 0};
9358 static gboolean lock = FALSE;
9359 MsgInfo *newmsginfo;
9361 gboolean target_locked = FALSE;
9362 gboolean err = FALSE;
9364 if (lock) return FALSE;
9366 if (compose->sending)
9369 draft = account_get_special_folder(compose->account, F_DRAFT);
9370 cm_return_val_if_fail(draft != NULL, FALSE);
9372 if (!g_mutex_trylock(compose->mutex)) {
9373 /* we don't want to lock the mutex once it's available,
9374 * because as the only other part of compose.c locking
9375 * it is compose_close - which means once unlocked,
9376 * the compose struct will be freed */
9377 debug_print("couldn't lock mutex, probably sending\n");
9383 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9384 G_DIR_SEPARATOR, compose);
9385 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9386 FILE_OP_ERROR(tmp, "fopen");
9390 /* chmod for security */
9391 if (change_file_mode_rw(fp, tmp) < 0) {
9392 FILE_OP_ERROR(tmp, "chmod");
9393 g_warning("can't change file mode\n");
9396 /* Save draft infos */
9397 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9398 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9400 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9401 gchar *savefolderid;
9403 savefolderid = compose_get_save_to(compose);
9404 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9405 g_free(savefolderid);
9407 if (compose->return_receipt) {
9408 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9410 if (compose->privacy_system) {
9411 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9412 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9413 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9416 /* Message-ID of message replying to */
9417 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9420 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9421 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9424 /* Message-ID of message forwarding to */
9425 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9428 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9429 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9433 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9434 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9436 /* end of headers */
9437 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9444 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9448 if (fclose(fp) == EOF) {
9452 if (compose->targetinfo) {
9453 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9454 flag.perm_flags = target_locked?MSG_LOCKED:0;
9456 flag.tmp_flags = MSG_DRAFT;
9458 folder_item_scan(draft);
9459 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9460 MsgInfo *tmpinfo = NULL;
9461 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9462 if (compose->msgid) {
9463 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9466 msgnum = tmpinfo->msgnum;
9467 procmsg_msginfo_free(tmpinfo);
9468 debug_print("got draft msgnum %d from scanning\n", msgnum);
9470 debug_print("didn't get draft msgnum after scanning\n");
9473 debug_print("got draft msgnum %d from adding\n", msgnum);
9479 if (action != COMPOSE_AUTO_SAVE) {
9480 if (action != COMPOSE_DRAFT_FOR_EXIT)
9481 alertpanel_error(_("Could not save draft."));
9484 gtkut_window_popup(compose->window);
9485 val = alertpanel_full(_("Could not save draft"),
9486 _("Could not save draft.\n"
9487 "Do you want to cancel exit or discard this email?"),
9488 _("_Cancel exit"), _("_Discard email"), NULL,
9489 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9490 if (val == G_ALERTALTERNATE) {
9492 g_mutex_unlock(compose->mutex); /* must be done before closing */
9493 compose_close(compose);
9497 g_mutex_unlock(compose->mutex); /* must be done before closing */
9506 if (compose->mode == COMPOSE_REEDIT) {
9507 compose_remove_reedit_target(compose, TRUE);
9510 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9513 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9515 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9517 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9518 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9519 procmsg_msginfo_set_flags(newmsginfo, 0,
9520 MSG_HAS_ATTACHMENT);
9522 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9523 compose_register_draft(newmsginfo);
9525 procmsg_msginfo_free(newmsginfo);
9528 folder_item_scan(draft);
9530 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9532 g_mutex_unlock(compose->mutex); /* must be done before closing */
9533 compose_close(compose);
9539 path = folder_item_fetch_msg(draft, msgnum);
9541 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9544 if (g_stat(path, &s) < 0) {
9545 FILE_OP_ERROR(path, "stat");
9551 procmsg_msginfo_free(compose->targetinfo);
9552 compose->targetinfo = procmsg_msginfo_new();
9553 compose->targetinfo->msgnum = msgnum;
9554 compose->targetinfo->size = (goffset)s.st_size;
9555 compose->targetinfo->mtime = s.st_mtime;
9556 compose->targetinfo->folder = draft;
9558 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9559 compose->mode = COMPOSE_REEDIT;
9561 if (action == COMPOSE_AUTO_SAVE) {
9562 compose->autosaved_draft = compose->targetinfo;
9564 compose->modified = FALSE;
9565 compose_set_title(compose);
9569 g_mutex_unlock(compose->mutex);
9573 void compose_clear_exit_drafts(void)
9575 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9576 DRAFTED_AT_EXIT, NULL);
9577 if (is_file_exist(filepath))
9578 claws_unlink(filepath);
9583 void compose_reopen_exit_drafts(void)
9585 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9586 DRAFTED_AT_EXIT, NULL);
9587 FILE *fp = g_fopen(filepath, "rb");
9591 while (fgets(buf, sizeof(buf), fp)) {
9592 gchar **parts = g_strsplit(buf, "\t", 2);
9593 const gchar *folder = parts[0];
9594 int msgnum = parts[1] ? atoi(parts[1]):-1;
9596 if (folder && *folder && msgnum > -1) {
9597 FolderItem *item = folder_find_item_from_identifier(folder);
9598 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9600 compose_reedit(info, FALSE);
9607 compose_clear_exit_drafts();
9610 static void compose_save_cb(GtkAction *action, gpointer data)
9612 Compose *compose = (Compose *)data;
9613 compose_draft(compose, COMPOSE_KEEP_EDITING);
9614 compose->rmode = COMPOSE_REEDIT;
9617 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9619 if (compose && file_list) {
9622 for ( tmp = file_list; tmp; tmp = tmp->next) {
9623 gchar *file = (gchar *) tmp->data;
9624 gchar *utf8_filename = conv_filename_to_utf8(file);
9625 compose_attach_append(compose, file, utf8_filename, NULL);
9626 compose_changed_cb(NULL, compose);
9631 g_free(utf8_filename);
9636 static void compose_attach_cb(GtkAction *action, gpointer data)
9638 Compose *compose = (Compose *)data;
9641 if (compose->redirect_filename != NULL)
9644 file_list = filesel_select_multiple_files_open(_("Select file"));
9647 compose_attach_from_list(compose, file_list, TRUE);
9648 g_list_free(file_list);
9652 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9654 Compose *compose = (Compose *)data;
9656 gint files_inserted = 0;
9658 file_list = filesel_select_multiple_files_open(_("Select file"));
9663 for ( tmp = file_list; tmp; tmp = tmp->next) {
9664 gchar *file = (gchar *) tmp->data;
9665 gchar *filedup = g_strdup(file);
9666 gchar *shortfile = g_path_get_basename(filedup);
9667 ComposeInsertResult res;
9668 /* insert the file if the file is short or if the user confirmed that
9669 he/she wants to insert the large file */
9670 res = compose_insert_file(compose, file);
9671 if (res == COMPOSE_INSERT_READ_ERROR) {
9672 alertpanel_error(_("File '%s' could not be read."), shortfile);
9673 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9674 alertpanel_error(_("File '%s' contained invalid characters\n"
9675 "for the current encoding, insertion may be incorrect."),
9677 } else if (res == COMPOSE_INSERT_SUCCESS)
9684 g_list_free(file_list);
9688 if (files_inserted > 0 && compose->gtkaspell &&
9689 compose->gtkaspell->check_while_typing)
9690 gtkaspell_highlight_all(compose->gtkaspell);
9694 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9696 Compose *compose = (Compose *)data;
9698 compose_insert_sig(compose, FALSE);
9701 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9705 Compose *compose = (Compose *)data;
9707 gtkut_widget_get_uposition(widget, &x, &y);
9708 if (!compose->batch) {
9709 prefs_common.compose_x = x;
9710 prefs_common.compose_y = y;
9712 if (compose->sending || compose->updating)
9714 compose_close_cb(NULL, compose);
9718 void compose_close_toolbar(Compose *compose)
9720 compose_close_cb(NULL, compose);
9723 static void compose_close_cb(GtkAction *action, gpointer data)
9725 Compose *compose = (Compose *)data;
9729 if (compose->exteditor_tag != -1) {
9730 if (!compose_ext_editor_kill(compose))
9735 if (compose->modified) {
9736 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9737 if (!g_mutex_trylock(compose->mutex)) {
9738 /* we don't want to lock the mutex once it's available,
9739 * because as the only other part of compose.c locking
9740 * it is compose_close - which means once unlocked,
9741 * the compose struct will be freed */
9742 debug_print("couldn't lock mutex, probably sending\n");
9746 val = alertpanel(_("Discard message"),
9747 _("This message has been modified. Discard it?"),
9748 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9750 val = alertpanel(_("Save changes"),
9751 _("This message has been modified. Save the latest changes?"),
9752 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9754 g_mutex_unlock(compose->mutex);
9756 case G_ALERTDEFAULT:
9757 if (prefs_common.autosave && !reedit)
9758 compose_remove_draft(compose);
9760 case G_ALERTALTERNATE:
9761 compose_draft(data, COMPOSE_QUIT_EDITING);
9768 compose_close(compose);
9771 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9773 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9774 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9775 Compose *compose = (Compose *) data;
9778 compose->out_encoding = (CharSet)value;
9781 static void compose_address_cb(GtkAction *action, gpointer data)
9783 Compose *compose = (Compose *)data;
9785 addressbook_open(compose);
9788 static void about_show_cb(GtkAction *action, gpointer data)
9793 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9795 Compose *compose = (Compose *)data;
9800 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9801 cm_return_if_fail(tmpl != NULL);
9803 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9805 val = alertpanel(_("Apply template"), msg,
9806 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9809 if (val == G_ALERTDEFAULT)
9810 compose_template_apply(compose, tmpl, TRUE);
9811 else if (val == G_ALERTALTERNATE)
9812 compose_template_apply(compose, tmpl, FALSE);
9815 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9817 Compose *compose = (Compose *)data;
9819 compose_exec_ext_editor(compose);
9822 static void compose_undo_cb(GtkAction *action, gpointer data)
9824 Compose *compose = (Compose *)data;
9825 gboolean prev_autowrap = compose->autowrap;
9827 compose->autowrap = FALSE;
9828 undo_undo(compose->undostruct);
9829 compose->autowrap = prev_autowrap;
9832 static void compose_redo_cb(GtkAction *action, gpointer data)
9834 Compose *compose = (Compose *)data;
9835 gboolean prev_autowrap = compose->autowrap;
9837 compose->autowrap = FALSE;
9838 undo_redo(compose->undostruct);
9839 compose->autowrap = prev_autowrap;
9842 static void entry_cut_clipboard(GtkWidget *entry)
9844 if (GTK_IS_EDITABLE(entry))
9845 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9846 else if (GTK_IS_TEXT_VIEW(entry))
9847 gtk_text_buffer_cut_clipboard(
9848 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9849 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9853 static void entry_copy_clipboard(GtkWidget *entry)
9855 if (GTK_IS_EDITABLE(entry))
9856 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9857 else if (GTK_IS_TEXT_VIEW(entry))
9858 gtk_text_buffer_copy_clipboard(
9859 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9860 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9863 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9864 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9866 if (GTK_IS_TEXT_VIEW(entry)) {
9867 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9868 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9869 GtkTextIter start_iter, end_iter;
9871 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9873 if (contents == NULL)
9876 /* we shouldn't delete the selection when middle-click-pasting, or we
9877 * can't mid-click-paste our own selection */
9878 if (clip != GDK_SELECTION_PRIMARY) {
9879 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9880 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9883 if (insert_place == NULL) {
9884 /* if insert_place isn't specified, insert at the cursor.
9885 * used for Ctrl-V pasting */
9886 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9887 start = gtk_text_iter_get_offset(&start_iter);
9888 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9890 /* if insert_place is specified, paste here.
9891 * used for mid-click-pasting */
9892 start = gtk_text_iter_get_offset(insert_place);
9893 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9894 if (prefs_common.primary_paste_unselects)
9895 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9899 /* paste unwrapped: mark the paste so it's not wrapped later */
9900 end = start + strlen(contents);
9901 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9902 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9903 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9904 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9905 /* rewrap paragraph now (after a mid-click-paste) */
9906 mark_start = gtk_text_buffer_get_insert(buffer);
9907 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9908 gtk_text_iter_backward_char(&start_iter);
9909 compose_beautify_paragraph(compose, &start_iter, TRUE);
9911 } else if (GTK_IS_EDITABLE(entry))
9912 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9914 compose->modified = TRUE;
9917 static void entry_allsel(GtkWidget *entry)
9919 if (GTK_IS_EDITABLE(entry))
9920 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9921 else if (GTK_IS_TEXT_VIEW(entry)) {
9922 GtkTextIter startiter, enditer;
9923 GtkTextBuffer *textbuf;
9925 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9926 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9927 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9929 gtk_text_buffer_move_mark_by_name(textbuf,
9930 "selection_bound", &startiter);
9931 gtk_text_buffer_move_mark_by_name(textbuf,
9932 "insert", &enditer);
9936 static void compose_cut_cb(GtkAction *action, gpointer data)
9938 Compose *compose = (Compose *)data;
9939 if (compose->focused_editable
9940 #ifndef GENERIC_UMPC
9941 && gtkut_widget_has_focus(compose->focused_editable)
9944 entry_cut_clipboard(compose->focused_editable);
9947 static void compose_copy_cb(GtkAction *action, gpointer data)
9949 Compose *compose = (Compose *)data;
9950 if (compose->focused_editable
9951 #ifndef GENERIC_UMPC
9952 && gtkut_widget_has_focus(compose->focused_editable)
9955 entry_copy_clipboard(compose->focused_editable);
9958 static void compose_paste_cb(GtkAction *action, gpointer data)
9960 Compose *compose = (Compose *)data;
9962 GtkTextBuffer *buffer;
9964 if (compose->focused_editable &&
9965 gtkut_widget_has_focus(compose->focused_editable))
9966 entry_paste_clipboard(compose, compose->focused_editable,
9967 prefs_common.linewrap_pastes,
9968 GDK_SELECTION_CLIPBOARD, NULL);
9972 if (gtkut_widget_has_focus(compose->text) &&
9973 compose->gtkaspell &&
9974 compose->gtkaspell->check_while_typing)
9975 gtkaspell_highlight_all(compose->gtkaspell);
9979 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9981 Compose *compose = (Compose *)data;
9982 gint wrap_quote = prefs_common.linewrap_quote;
9983 if (compose->focused_editable
9984 #ifndef GENERIC_UMPC
9985 && gtkut_widget_has_focus(compose->focused_editable)
9988 /* let text_insert() (called directly or at a later time
9989 * after the gtk_editable_paste_clipboard) know that
9990 * text is to be inserted as a quotation. implemented
9991 * by using a simple refcount... */
9992 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9993 G_OBJECT(compose->focused_editable),
9994 "paste_as_quotation"));
9995 g_object_set_data(G_OBJECT(compose->focused_editable),
9996 "paste_as_quotation",
9997 GINT_TO_POINTER(paste_as_quotation + 1));
9998 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9999 entry_paste_clipboard(compose, compose->focused_editable,
10000 prefs_common.linewrap_pastes,
10001 GDK_SELECTION_CLIPBOARD, NULL);
10002 prefs_common.linewrap_quote = wrap_quote;
10006 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10008 Compose *compose = (Compose *)data;
10009 gint prev_autowrap;
10010 GtkTextBuffer *buffer;
10012 if (compose->focused_editable
10013 #ifndef GENERIC_UMPC
10014 && gtkut_widget_has_focus(compose->focused_editable)
10017 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10018 GDK_SELECTION_CLIPBOARD, NULL);
10022 if (gtkut_widget_has_focus(compose->text) &&
10023 compose->gtkaspell &&
10024 compose->gtkaspell->check_while_typing)
10025 gtkaspell_highlight_all(compose->gtkaspell);
10029 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10031 Compose *compose = (Compose *)data;
10032 gint prev_autowrap;
10033 GtkTextBuffer *buffer;
10035 if (compose->focused_editable
10036 #ifndef GENERIC_UMPC
10037 && gtkut_widget_has_focus(compose->focused_editable)
10040 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10041 GDK_SELECTION_CLIPBOARD, NULL);
10045 if (gtkut_widget_has_focus(compose->text) &&
10046 compose->gtkaspell &&
10047 compose->gtkaspell->check_while_typing)
10048 gtkaspell_highlight_all(compose->gtkaspell);
10052 static void compose_allsel_cb(GtkAction *action, gpointer data)
10054 Compose *compose = (Compose *)data;
10055 if (compose->focused_editable
10056 #ifndef GENERIC_UMPC
10057 && gtkut_widget_has_focus(compose->focused_editable)
10060 entry_allsel(compose->focused_editable);
10063 static void textview_move_beginning_of_line (GtkTextView *text)
10065 GtkTextBuffer *buffer;
10069 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10071 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10072 mark = gtk_text_buffer_get_insert(buffer);
10073 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10074 gtk_text_iter_set_line_offset(&ins, 0);
10075 gtk_text_buffer_place_cursor(buffer, &ins);
10078 static void textview_move_forward_character (GtkTextView *text)
10080 GtkTextBuffer *buffer;
10084 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10086 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10087 mark = gtk_text_buffer_get_insert(buffer);
10088 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10089 if (gtk_text_iter_forward_cursor_position(&ins))
10090 gtk_text_buffer_place_cursor(buffer, &ins);
10093 static void textview_move_backward_character (GtkTextView *text)
10095 GtkTextBuffer *buffer;
10099 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10101 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10102 mark = gtk_text_buffer_get_insert(buffer);
10103 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10104 if (gtk_text_iter_backward_cursor_position(&ins))
10105 gtk_text_buffer_place_cursor(buffer, &ins);
10108 static void textview_move_forward_word (GtkTextView *text)
10110 GtkTextBuffer *buffer;
10115 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10117 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10118 mark = gtk_text_buffer_get_insert(buffer);
10119 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10120 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10121 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10122 gtk_text_iter_backward_word_start(&ins);
10123 gtk_text_buffer_place_cursor(buffer, &ins);
10127 static void textview_move_backward_word (GtkTextView *text)
10129 GtkTextBuffer *buffer;
10134 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10136 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10137 mark = gtk_text_buffer_get_insert(buffer);
10138 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10139 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10140 if (gtk_text_iter_backward_word_starts(&ins, 1))
10141 gtk_text_buffer_place_cursor(buffer, &ins);
10144 static void textview_move_end_of_line (GtkTextView *text)
10146 GtkTextBuffer *buffer;
10150 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10152 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10153 mark = gtk_text_buffer_get_insert(buffer);
10154 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10155 if (gtk_text_iter_forward_to_line_end(&ins))
10156 gtk_text_buffer_place_cursor(buffer, &ins);
10159 static void textview_move_next_line (GtkTextView *text)
10161 GtkTextBuffer *buffer;
10166 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10168 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10169 mark = gtk_text_buffer_get_insert(buffer);
10170 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10171 offset = gtk_text_iter_get_line_offset(&ins);
10172 if (gtk_text_iter_forward_line(&ins)) {
10173 gtk_text_iter_set_line_offset(&ins, offset);
10174 gtk_text_buffer_place_cursor(buffer, &ins);
10178 static void textview_move_previous_line (GtkTextView *text)
10180 GtkTextBuffer *buffer;
10185 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10187 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10188 mark = gtk_text_buffer_get_insert(buffer);
10189 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10190 offset = gtk_text_iter_get_line_offset(&ins);
10191 if (gtk_text_iter_backward_line(&ins)) {
10192 gtk_text_iter_set_line_offset(&ins, offset);
10193 gtk_text_buffer_place_cursor(buffer, &ins);
10197 static void textview_delete_forward_character (GtkTextView *text)
10199 GtkTextBuffer *buffer;
10201 GtkTextIter ins, end_iter;
10203 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10205 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10206 mark = gtk_text_buffer_get_insert(buffer);
10207 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10209 if (gtk_text_iter_forward_char(&end_iter)) {
10210 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10214 static void textview_delete_backward_character (GtkTextView *text)
10216 GtkTextBuffer *buffer;
10218 GtkTextIter ins, end_iter;
10220 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10222 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10223 mark = gtk_text_buffer_get_insert(buffer);
10224 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10226 if (gtk_text_iter_backward_char(&end_iter)) {
10227 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10231 static void textview_delete_forward_word (GtkTextView *text)
10233 GtkTextBuffer *buffer;
10235 GtkTextIter ins, end_iter;
10237 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10239 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10240 mark = gtk_text_buffer_get_insert(buffer);
10241 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10243 if (gtk_text_iter_forward_word_end(&end_iter)) {
10244 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10248 static void textview_delete_backward_word (GtkTextView *text)
10250 GtkTextBuffer *buffer;
10252 GtkTextIter ins, end_iter;
10254 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10256 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10257 mark = gtk_text_buffer_get_insert(buffer);
10258 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10260 if (gtk_text_iter_backward_word_start(&end_iter)) {
10261 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10265 static void textview_delete_line (GtkTextView *text)
10267 GtkTextBuffer *buffer;
10269 GtkTextIter ins, start_iter, end_iter;
10271 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10273 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10274 mark = gtk_text_buffer_get_insert(buffer);
10275 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10278 gtk_text_iter_set_line_offset(&start_iter, 0);
10281 if (gtk_text_iter_ends_line(&end_iter)){
10282 if (!gtk_text_iter_forward_char(&end_iter))
10283 gtk_text_iter_backward_char(&start_iter);
10286 gtk_text_iter_forward_to_line_end(&end_iter);
10287 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10290 static void textview_delete_to_line_end (GtkTextView *text)
10292 GtkTextBuffer *buffer;
10294 GtkTextIter ins, end_iter;
10296 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10298 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10299 mark = gtk_text_buffer_get_insert(buffer);
10300 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10302 if (gtk_text_iter_ends_line(&end_iter))
10303 gtk_text_iter_forward_char(&end_iter);
10305 gtk_text_iter_forward_to_line_end(&end_iter);
10306 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10309 #define DO_ACTION(name, act) { \
10310 if(!strcmp(name, a_name)) { \
10314 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10316 const gchar *a_name = gtk_action_get_name(action);
10317 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10318 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10319 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10320 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10321 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10322 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10323 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10324 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10325 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10326 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10327 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10328 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10329 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10330 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10334 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10336 Compose *compose = (Compose *)data;
10337 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10338 ComposeCallAdvancedAction action = -1;
10340 action = compose_call_advanced_action_from_path(gaction);
10343 void (*do_action) (GtkTextView *text);
10344 } action_table[] = {
10345 {textview_move_beginning_of_line},
10346 {textview_move_forward_character},
10347 {textview_move_backward_character},
10348 {textview_move_forward_word},
10349 {textview_move_backward_word},
10350 {textview_move_end_of_line},
10351 {textview_move_next_line},
10352 {textview_move_previous_line},
10353 {textview_delete_forward_character},
10354 {textview_delete_backward_character},
10355 {textview_delete_forward_word},
10356 {textview_delete_backward_word},
10357 {textview_delete_line},
10358 {textview_delete_to_line_end}
10361 if (!gtkut_widget_has_focus(GTK_WIDGET(text))) return;
10363 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10364 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10365 if (action_table[action].do_action)
10366 action_table[action].do_action(text);
10368 g_warning("Not implemented yet.");
10372 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10376 if (GTK_IS_EDITABLE(widget)) {
10377 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10378 gtk_editable_set_position(GTK_EDITABLE(widget),
10381 if (widget->parent && widget->parent->parent
10382 && widget->parent->parent->parent) {
10383 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10384 gint y = widget->allocation.y;
10385 gint height = widget->allocation.height;
10386 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10387 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10389 if (y < (int)shown->value) {
10390 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10392 if (y + height > (int)shown->value + (int)shown->page_size) {
10393 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10394 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10395 y + height - (int)shown->page_size - 1);
10397 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10398 (int)shown->upper - (int)shown->page_size - 1);
10405 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10406 compose->focused_editable = widget;
10408 #ifdef GENERIC_UMPC
10409 if (GTK_IS_TEXT_VIEW(widget)
10410 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10411 g_object_ref(compose->notebook);
10412 g_object_ref(compose->edit_vbox);
10413 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10414 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10415 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10416 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10417 g_object_unref(compose->notebook);
10418 g_object_unref(compose->edit_vbox);
10419 g_signal_handlers_block_by_func(G_OBJECT(widget),
10420 G_CALLBACK(compose_grab_focus_cb),
10422 gtk_widget_grab_focus(widget);
10423 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10424 G_CALLBACK(compose_grab_focus_cb),
10426 } else if (!GTK_IS_TEXT_VIEW(widget)
10427 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10428 g_object_ref(compose->notebook);
10429 g_object_ref(compose->edit_vbox);
10430 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10431 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10432 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10433 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10434 g_object_unref(compose->notebook);
10435 g_object_unref(compose->edit_vbox);
10436 g_signal_handlers_block_by_func(G_OBJECT(widget),
10437 G_CALLBACK(compose_grab_focus_cb),
10439 gtk_widget_grab_focus(widget);
10440 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10441 G_CALLBACK(compose_grab_focus_cb),
10447 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10449 compose->modified = TRUE;
10450 // compose_beautify_paragraph(compose, NULL, TRUE);
10451 #ifndef GENERIC_UMPC
10452 compose_set_title(compose);
10456 static void compose_wrap_cb(GtkAction *action, gpointer data)
10458 Compose *compose = (Compose *)data;
10459 compose_beautify_paragraph(compose, NULL, TRUE);
10462 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10464 Compose *compose = (Compose *)data;
10465 compose_wrap_all_full(compose, TRUE);
10468 static void compose_find_cb(GtkAction *action, gpointer data)
10470 Compose *compose = (Compose *)data;
10472 message_search_compose(compose);
10475 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10478 Compose *compose = (Compose *)data;
10479 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10480 if (compose->autowrap)
10481 compose_wrap_all_full(compose, TRUE);
10482 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10485 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10488 Compose *compose = (Compose *)data;
10489 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10492 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10494 Compose *compose = (Compose *)data;
10496 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10499 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10501 Compose *compose = (Compose *)data;
10503 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10506 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10508 g_free(compose->privacy_system);
10510 compose->privacy_system = g_strdup(account->default_privacy_system);
10511 compose_update_privacy_system_menu_item(compose, warn);
10514 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10516 Compose *compose = (Compose *)data;
10518 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10519 gtk_widget_show(compose->ruler_hbox);
10520 prefs_common.show_ruler = TRUE;
10522 gtk_widget_hide(compose->ruler_hbox);
10523 gtk_widget_queue_resize(compose->edit_vbox);
10524 prefs_common.show_ruler = FALSE;
10528 static void compose_attach_drag_received_cb (GtkWidget *widget,
10529 GdkDragContext *context,
10532 GtkSelectionData *data,
10535 gpointer user_data)
10537 Compose *compose = (Compose *)user_data;
10540 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10542 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10544 ) && gtk_drag_get_source_widget(context) !=
10545 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10546 list = uri_list_extract_filenames((const gchar *)data->data);
10547 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10548 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10549 compose_attach_append
10550 (compose, (const gchar *)tmp->data,
10551 utf8_filename, NULL);
10552 g_free(utf8_filename);
10554 if (list) compose_changed_cb(NULL, compose);
10555 list_free_strings(list);
10557 } else if (gtk_drag_get_source_widget(context)
10558 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10559 /* comes from our summaryview */
10560 SummaryView * summaryview = NULL;
10561 GSList * list = NULL, *cur = NULL;
10563 if (mainwindow_get_mainwindow())
10564 summaryview = mainwindow_get_mainwindow()->summaryview;
10567 list = summary_get_selected_msg_list(summaryview);
10569 for (cur = list; cur; cur = cur->next) {
10570 MsgInfo *msginfo = (MsgInfo *)cur->data;
10571 gchar *file = NULL;
10573 file = procmsg_get_message_file_full(msginfo,
10576 compose_attach_append(compose, (const gchar *)file,
10577 (const gchar *)file, "message/rfc822");
10581 g_slist_free(list);
10585 static gboolean compose_drag_drop(GtkWidget *widget,
10586 GdkDragContext *drag_context,
10588 guint time, gpointer user_data)
10590 /* not handling this signal makes compose_insert_drag_received_cb
10595 static void compose_insert_drag_received_cb (GtkWidget *widget,
10596 GdkDragContext *drag_context,
10599 GtkSelectionData *data,
10602 gpointer user_data)
10604 Compose *compose = (Compose *)user_data;
10607 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10610 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10612 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10614 AlertValue val = G_ALERTDEFAULT;
10616 list = uri_list_extract_filenames((const gchar *)data->data);
10617 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10618 /* Assume a list of no files, and data has ://, is a remote link */
10619 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10620 gchar *tmpfile = get_tmp_file();
10621 str_write_to_file(tmpdata, tmpfile);
10623 compose_insert_file(compose, tmpfile);
10624 claws_unlink(tmpfile);
10626 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10627 compose_beautify_paragraph(compose, NULL, TRUE);
10630 switch (prefs_common.compose_dnd_mode) {
10631 case COMPOSE_DND_ASK:
10632 val = alertpanel_full(_("Insert or attach?"),
10633 _("Do you want to insert the contents of the file(s) "
10634 "into the message body, or attach it to the email?"),
10635 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10636 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10638 case COMPOSE_DND_INSERT:
10639 val = G_ALERTALTERNATE;
10641 case COMPOSE_DND_ATTACH:
10642 val = G_ALERTOTHER;
10645 /* unexpected case */
10646 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10649 if (val & G_ALERTDISABLE) {
10650 val &= ~G_ALERTDISABLE;
10651 /* remember what action to perform by default, only if we don't click Cancel */
10652 if (val == G_ALERTALTERNATE)
10653 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10654 else if (val == G_ALERTOTHER)
10655 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10658 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10659 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10660 list_free_strings(list);
10663 } else if (val == G_ALERTOTHER) {
10664 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10665 list_free_strings(list);
10670 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10671 compose_insert_file(compose, (const gchar *)tmp->data);
10673 list_free_strings(list);
10675 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10680 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10683 static void compose_header_drag_received_cb (GtkWidget *widget,
10684 GdkDragContext *drag_context,
10687 GtkSelectionData *data,
10690 gpointer user_data)
10692 GtkEditable *entry = (GtkEditable *)user_data;
10693 gchar *email = (gchar *)data->data;
10695 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10698 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10699 gchar *decoded=g_new(gchar, strlen(email));
10702 email += strlen("mailto:");
10703 decode_uri(decoded, email); /* will fit */
10704 gtk_editable_delete_text(entry, 0, -1);
10705 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10706 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10710 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10713 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10715 Compose *compose = (Compose *)data;
10717 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10718 compose->return_receipt = TRUE;
10720 compose->return_receipt = FALSE;
10723 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10725 Compose *compose = (Compose *)data;
10727 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10728 compose->remove_references = TRUE;
10730 compose->remove_references = FALSE;
10733 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10734 ComposeHeaderEntry *headerentry)
10736 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10740 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10741 GdkEventKey *event,
10742 ComposeHeaderEntry *headerentry)
10744 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10745 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10746 !(event->state & GDK_MODIFIER_MASK) &&
10747 (event->keyval == GDK_BackSpace) &&
10748 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10749 gtk_container_remove
10750 (GTK_CONTAINER(headerentry->compose->header_table),
10751 headerentry->combo);
10752 gtk_container_remove
10753 (GTK_CONTAINER(headerentry->compose->header_table),
10754 headerentry->entry);
10755 headerentry->compose->header_list =
10756 g_slist_remove(headerentry->compose->header_list,
10758 g_free(headerentry);
10759 } else if (event->keyval == GDK_Tab) {
10760 if (headerentry->compose->header_last == headerentry) {
10761 /* Override default next focus, and give it to subject_entry
10762 * instead of notebook tabs
10764 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10765 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10772 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10773 ComposeHeaderEntry *headerentry)
10775 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10776 compose_create_header_entry(headerentry->compose);
10777 g_signal_handlers_disconnect_matched
10778 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10779 0, 0, NULL, NULL, headerentry);
10781 /* Automatically scroll down */
10782 GTK_EVENTS_FLUSH();
10783 compose_show_first_last_header(headerentry->compose, FALSE);
10789 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10791 GtkAdjustment *vadj;
10793 cm_return_if_fail(compose);
10794 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10795 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10796 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10797 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10798 gtk_adjustment_changed(vadj);
10801 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10802 const gchar *text, gint len, Compose *compose)
10804 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10805 (G_OBJECT(compose->text), "paste_as_quotation"));
10808 cm_return_if_fail(text != NULL);
10810 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10811 G_CALLBACK(text_inserted),
10813 if (paste_as_quotation) {
10815 const gchar *qmark;
10817 GtkTextIter start_iter;
10820 len = strlen(text);
10822 new_text = g_strndup(text, len);
10824 qmark = compose_quote_char_from_context(compose);
10826 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10827 gtk_text_buffer_place_cursor(buffer, iter);
10829 pos = gtk_text_iter_get_offset(iter);
10831 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10832 _("Quote format error at line %d."));
10833 quote_fmt_reset_vartable();
10835 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10836 GINT_TO_POINTER(paste_as_quotation - 1));
10838 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10839 gtk_text_buffer_place_cursor(buffer, iter);
10840 gtk_text_buffer_delete_mark(buffer, mark);
10842 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10843 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10844 compose_beautify_paragraph(compose, &start_iter, FALSE);
10845 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10846 gtk_text_buffer_delete_mark(buffer, mark);
10848 if (strcmp(text, "\n") || compose->automatic_break
10849 || gtk_text_iter_starts_line(iter)) {
10850 GtkTextIter before_ins;
10851 gtk_text_buffer_insert(buffer, iter, text, len);
10852 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10853 before_ins = *iter;
10854 gtk_text_iter_backward_chars(&before_ins, len);
10855 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10858 /* check if the preceding is just whitespace or quote */
10859 GtkTextIter start_line;
10860 gchar *tmp = NULL, *quote = NULL;
10861 gint quote_len = 0, is_normal = 0;
10862 start_line = *iter;
10863 gtk_text_iter_set_line_offset(&start_line, 0);
10864 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10867 if (*tmp == '\0') {
10870 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10878 gtk_text_buffer_insert(buffer, iter, text, len);
10880 gtk_text_buffer_insert_with_tags_by_name(buffer,
10881 iter, text, len, "no_join", NULL);
10886 if (!paste_as_quotation) {
10887 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10888 compose_beautify_paragraph(compose, iter, FALSE);
10889 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10890 gtk_text_buffer_delete_mark(buffer, mark);
10893 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10894 G_CALLBACK(text_inserted),
10896 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10898 if (prefs_common.autosave &&
10899 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10900 compose->draft_timeout_tag != -2 /* disabled while loading */)
10901 compose->draft_timeout_tag = g_timeout_add
10902 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10904 static gint compose_defer_auto_save_draft(Compose *compose)
10906 compose->draft_timeout_tag = -1;
10907 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10912 static void compose_check_all(GtkAction *action, gpointer data)
10914 Compose *compose = (Compose *)data;
10915 if (!compose->gtkaspell)
10918 if (gtkut_widget_has_focus(compose->subject_entry))
10919 claws_spell_entry_check_all(
10920 CLAWS_SPELL_ENTRY(compose->subject_entry));
10922 gtkaspell_check_all(compose->gtkaspell);
10925 static void compose_highlight_all(GtkAction *action, gpointer data)
10927 Compose *compose = (Compose *)data;
10928 if (compose->gtkaspell) {
10929 claws_spell_entry_recheck_all(
10930 CLAWS_SPELL_ENTRY(compose->subject_entry));
10931 gtkaspell_highlight_all(compose->gtkaspell);
10935 static void compose_check_backwards(GtkAction *action, gpointer data)
10937 Compose *compose = (Compose *)data;
10938 if (!compose->gtkaspell) {
10939 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10943 if (gtkut_widget_has_focus(compose->subject_entry))
10944 claws_spell_entry_check_backwards(
10945 CLAWS_SPELL_ENTRY(compose->subject_entry));
10947 gtkaspell_check_backwards(compose->gtkaspell);
10950 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10952 Compose *compose = (Compose *)data;
10953 if (!compose->gtkaspell) {
10954 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10958 if (gtkut_widget_has_focus(compose->subject_entry))
10959 claws_spell_entry_check_forwards_go(
10960 CLAWS_SPELL_ENTRY(compose->subject_entry));
10962 gtkaspell_check_forwards_go(compose->gtkaspell);
10967 *\brief Guess originating forward account from MsgInfo and several
10968 * "common preference" settings. Return NULL if no guess.
10970 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10972 PrefsAccount *account = NULL;
10974 cm_return_val_if_fail(msginfo, NULL);
10975 cm_return_val_if_fail(msginfo->folder, NULL);
10976 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10978 if (msginfo->folder->prefs->enable_default_account)
10979 account = account_find_from_id(msginfo->folder->prefs->default_account);
10982 account = msginfo->folder->folder->account;
10984 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10986 Xstrdup_a(to, msginfo->to, return NULL);
10987 extract_address(to);
10988 account = account_find_from_address(to, FALSE);
10991 if (!account && prefs_common.forward_account_autosel) {
10992 gchar cc[BUFFSIZE];
10993 if (!procheader_get_header_from_msginfo
10994 (msginfo, cc,sizeof cc , "Cc:")) {
10995 gchar *buf = cc + strlen("Cc:");
10996 extract_address(buf);
10997 account = account_find_from_address(buf, FALSE);
11001 if (!account && prefs_common.forward_account_autosel) {
11002 gchar deliveredto[BUFFSIZE];
11003 if (!procheader_get_header_from_msginfo
11004 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11005 gchar *buf = deliveredto + strlen("Delivered-To:");
11006 extract_address(buf);
11007 account = account_find_from_address(buf, FALSE);
11014 gboolean compose_close(Compose *compose)
11018 if (!g_mutex_trylock(compose->mutex)) {
11019 /* we have to wait for the (possibly deferred by auto-save)
11020 * drafting to be done, before destroying the compose under
11022 debug_print("waiting for drafting to finish...\n");
11023 compose_allow_user_actions(compose, FALSE);
11024 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11027 cm_return_val_if_fail(compose, FALSE);
11028 gtkut_widget_get_uposition(compose->window, &x, &y);
11029 if (!compose->batch) {
11030 prefs_common.compose_x = x;
11031 prefs_common.compose_y = y;
11033 g_mutex_unlock(compose->mutex);
11034 compose_destroy(compose);
11039 * Add entry field for each address in list.
11040 * \param compose E-Mail composition object.
11041 * \param listAddress List of (formatted) E-Mail addresses.
11043 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11046 node = listAddress;
11048 addr = ( gchar * ) node->data;
11049 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11050 node = g_list_next( node );
11054 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11055 guint action, gboolean opening_multiple)
11057 gchar *body = NULL;
11058 GSList *new_msglist = NULL;
11059 MsgInfo *tmp_msginfo = NULL;
11060 gboolean originally_enc = FALSE;
11061 gboolean originally_sig = FALSE;
11062 Compose *compose = NULL;
11063 gchar *s_system = NULL;
11065 cm_return_if_fail(msgview != NULL);
11067 cm_return_if_fail(msginfo_list != NULL);
11069 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11070 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11071 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11073 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11074 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11075 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11076 orig_msginfo, mimeinfo);
11077 if (tmp_msginfo != NULL) {
11078 new_msglist = g_slist_append(NULL, tmp_msginfo);
11080 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11081 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11082 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11084 tmp_msginfo->folder = orig_msginfo->folder;
11085 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11086 if (orig_msginfo->tags) {
11087 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11088 tmp_msginfo->folder->tags_dirty = TRUE;
11094 if (!opening_multiple)
11095 body = messageview_get_selection(msgview);
11098 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11099 procmsg_msginfo_free(tmp_msginfo);
11100 g_slist_free(new_msglist);
11102 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11104 if (compose && originally_enc) {
11105 compose_force_encryption(compose, compose->account, FALSE, s_system);
11108 if (compose && originally_sig && compose->account->default_sign_reply) {
11109 compose_force_signing(compose, compose->account, s_system);
11113 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11116 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11119 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11120 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11121 GSList *cur = msginfo_list;
11122 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11123 "messages. Opening the windows "
11124 "could take some time. Do you "
11125 "want to continue?"),
11126 g_slist_length(msginfo_list));
11127 if (g_slist_length(msginfo_list) > 9
11128 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11129 != G_ALERTALTERNATE) {
11134 /* We'll open multiple compose windows */
11135 /* let the WM place the next windows */
11136 compose_force_window_origin = FALSE;
11137 for (; cur; cur = cur->next) {
11139 tmplist.data = cur->data;
11140 tmplist.next = NULL;
11141 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11143 compose_force_window_origin = TRUE;
11145 /* forwarding multiple mails as attachments is done via a
11146 * single compose window */
11147 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11151 void compose_check_for_email_account(Compose *compose)
11153 PrefsAccount *ac = NULL, *curr = NULL;
11159 if (compose->account && compose->account->protocol == A_NNTP) {
11160 ac = account_get_cur_account();
11161 if (ac->protocol == A_NNTP) {
11162 list = account_get_list();
11164 for( ; list != NULL ; list = g_list_next(list)) {
11165 curr = (PrefsAccount *) list->data;
11166 if (curr->protocol != A_NNTP) {
11172 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11177 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11178 const gchar *address)
11180 GSList *msginfo_list = NULL;
11181 gchar *body = messageview_get_selection(msgview);
11184 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11186 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11187 compose_check_for_email_account(compose);
11188 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11189 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11190 compose_reply_set_subject(compose, msginfo);
11193 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11196 void compose_set_position(Compose *compose, gint pos)
11198 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11200 gtkut_text_view_set_position(text, pos);
11203 gboolean compose_search_string(Compose *compose,
11204 const gchar *str, gboolean case_sens)
11206 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11208 return gtkut_text_view_search_string(text, str, case_sens);
11211 gboolean compose_search_string_backward(Compose *compose,
11212 const gchar *str, gboolean case_sens)
11214 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11216 return gtkut_text_view_search_string_backward(text, str, case_sens);
11219 /* allocate a msginfo structure and populate its data from a compose data structure */
11220 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11222 MsgInfo *newmsginfo;
11224 gchar buf[BUFFSIZE];
11226 cm_return_val_if_fail( compose != NULL, NULL );
11228 newmsginfo = procmsg_msginfo_new();
11231 get_rfc822_date(buf, sizeof(buf));
11232 newmsginfo->date = g_strdup(buf);
11235 if (compose->from_name) {
11236 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11237 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11241 if (compose->subject_entry)
11242 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11244 /* to, cc, reply-to, newsgroups */
11245 for (list = compose->header_list; list; list = list->next) {
11246 gchar *header = gtk_editable_get_chars(
11248 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11249 gchar *entry = gtk_editable_get_chars(
11250 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11252 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11253 if ( newmsginfo->to == NULL ) {
11254 newmsginfo->to = g_strdup(entry);
11255 } else if (entry && *entry) {
11256 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11257 g_free(newmsginfo->to);
11258 newmsginfo->to = tmp;
11261 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11262 if ( newmsginfo->cc == NULL ) {
11263 newmsginfo->cc = g_strdup(entry);
11264 } else if (entry && *entry) {
11265 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11266 g_free(newmsginfo->cc);
11267 newmsginfo->cc = tmp;
11270 if ( strcasecmp(header,
11271 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11272 if ( newmsginfo->newsgroups == NULL ) {
11273 newmsginfo->newsgroups = g_strdup(entry);
11274 } else if (entry && *entry) {
11275 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11276 g_free(newmsginfo->newsgroups);
11277 newmsginfo->newsgroups = tmp;
11285 /* other data is unset */
11291 /* update compose's dictionaries from folder dict settings */
11292 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11293 FolderItem *folder_item)
11295 cm_return_if_fail(compose != NULL);
11297 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11298 FolderItemPrefs *prefs = folder_item->prefs;
11300 if (prefs->enable_default_dictionary)
11301 gtkaspell_change_dict(compose->gtkaspell,
11302 prefs->default_dictionary, FALSE);
11303 if (folder_item->prefs->enable_default_alt_dictionary)
11304 gtkaspell_change_alt_dict(compose->gtkaspell,
11305 prefs->default_alt_dictionary);
11306 if (prefs->enable_default_dictionary
11307 || prefs->enable_default_alt_dictionary)
11308 compose_spell_menu_changed(compose);