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);
3002 gtk_text_buffer_place_cursor(buffer, &iter);
3004 compose->autowrap = prev_autowrap;
3005 if (compose->autowrap && rewrap)
3006 compose_wrap_all(compose);
3013 SIGNAL_UNBLOCK(buffer);
3015 procmsg_msginfo_free( dummyinfo );
3020 /* if ml_post is of type addr@host and from is of type
3021 * addr-anything@host, return TRUE
3023 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3025 gchar *left_ml = NULL;
3026 gchar *right_ml = NULL;
3027 gchar *left_from = NULL;
3028 gchar *right_from = NULL;
3029 gboolean result = FALSE;
3031 if (!ml_post || !from)
3034 left_ml = g_strdup(ml_post);
3035 if (strstr(left_ml, "@")) {
3036 right_ml = strstr(left_ml, "@")+1;
3037 *(strstr(left_ml, "@")) = '\0';
3040 left_from = g_strdup(from);
3041 if (strstr(left_from, "@")) {
3042 right_from = strstr(left_from, "@")+1;
3043 *(strstr(left_from, "@")) = '\0';
3046 if (left_ml && left_from && right_ml && right_from
3047 && !strncmp(left_from, left_ml, strlen(left_ml))
3048 && !strcmp(right_from, right_ml)) {
3057 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3058 gboolean respect_default_to)
3062 if (!folder || !folder->prefs)
3065 if (respect_default_to && folder->prefs->enable_default_to) {
3066 compose_entry_append(compose, folder->prefs->default_to,
3067 COMPOSE_TO, PREF_FOLDER);
3068 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3070 if (folder->prefs->enable_default_cc)
3071 compose_entry_append(compose, folder->prefs->default_cc,
3072 COMPOSE_CC, PREF_FOLDER);
3073 if (folder->prefs->enable_default_bcc)
3074 compose_entry_append(compose, folder->prefs->default_bcc,
3075 COMPOSE_BCC, PREF_FOLDER);
3076 if (folder->prefs->enable_default_replyto)
3077 compose_entry_append(compose, folder->prefs->default_replyto,
3078 COMPOSE_REPLYTO, PREF_FOLDER);
3081 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3086 if (!compose || !msginfo)
3089 if (msginfo->subject && *msginfo->subject) {
3090 buf = p = g_strdup(msginfo->subject);
3091 p += subject_get_prefix_length(p);
3092 memmove(buf, p, strlen(p) + 1);
3094 buf2 = g_strdup_printf("Re: %s", buf);
3095 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3100 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3103 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3104 gboolean to_all, gboolean to_ml,
3106 gboolean followup_and_reply_to)
3108 GSList *cc_list = NULL;
3111 gchar *replyto = NULL;
3112 gchar *ac_email = NULL;
3114 gboolean reply_to_ml = FALSE;
3115 gboolean default_reply_to = FALSE;
3117 cm_return_if_fail(compose->account != NULL);
3118 cm_return_if_fail(msginfo != NULL);
3120 reply_to_ml = to_ml && compose->ml_post;
3122 default_reply_to = msginfo->folder &&
3123 msginfo->folder->prefs->enable_default_reply_to;
3125 if (compose->account->protocol != A_NNTP) {
3126 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3128 if (reply_to_ml && !default_reply_to) {
3130 gboolean is_subscr = is_subscription(compose->ml_post,
3133 /* normal answer to ml post with a reply-to */
3134 compose_entry_append(compose,
3136 COMPOSE_TO, PREF_ML);
3137 if (compose->replyto)
3138 compose_entry_append(compose,
3140 COMPOSE_CC, PREF_ML);
3142 /* answer to subscription confirmation */
3143 if (compose->replyto)
3144 compose_entry_append(compose,
3146 COMPOSE_TO, PREF_ML);
3147 else if (msginfo->from)
3148 compose_entry_append(compose,
3150 COMPOSE_TO, PREF_ML);
3153 else if (!(to_all || to_sender) && default_reply_to) {
3154 compose_entry_append(compose,
3155 msginfo->folder->prefs->default_reply_to,
3156 COMPOSE_TO, PREF_FOLDER);
3157 compose_entry_mark_default_to(compose,
3158 msginfo->folder->prefs->default_reply_to);
3163 Xstrdup_a(tmp1, msginfo->from, return);
3164 extract_address(tmp1);
3165 if (to_all || to_sender ||
3166 !account_find_from_address(tmp1, FALSE))
3167 compose_entry_append(compose,
3168 (compose->replyto && !to_sender)
3169 ? compose->replyto :
3170 msginfo->from ? msginfo->from : "",
3171 COMPOSE_TO, PREF_NONE);
3172 else if (!to_all && !to_sender) {
3173 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3174 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3175 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3176 if (compose->replyto) {
3177 compose_entry_append(compose,
3179 COMPOSE_TO, PREF_NONE);
3181 compose_entry_append(compose,
3182 msginfo->from ? msginfo->from : "",
3183 COMPOSE_TO, PREF_NONE);
3186 /* replying to own mail, use original recp */
3187 compose_entry_append(compose,
3188 msginfo->to ? msginfo->to : "",
3189 COMPOSE_TO, PREF_NONE);
3190 compose_entry_append(compose,
3191 msginfo->cc ? msginfo->cc : "",
3192 COMPOSE_CC, PREF_NONE);
3197 if (to_sender || (compose->followup_to &&
3198 !strncmp(compose->followup_to, "poster", 6)))
3199 compose_entry_append
3201 (compose->replyto ? compose->replyto :
3202 msginfo->from ? msginfo->from : ""),
3203 COMPOSE_TO, PREF_NONE);
3205 else if (followup_and_reply_to || to_all) {
3206 compose_entry_append
3208 (compose->replyto ? compose->replyto :
3209 msginfo->from ? msginfo->from : ""),
3210 COMPOSE_TO, PREF_NONE);
3212 compose_entry_append
3214 compose->followup_to ? compose->followup_to :
3215 compose->newsgroups ? compose->newsgroups : "",
3216 COMPOSE_NEWSGROUPS, PREF_NONE);
3217 } else if (reply_to_ml)
3218 compose_entry_append(compose,
3219 compose->ml_post, COMPOSE_TO, PREF_ML);
3221 compose_entry_append
3223 compose->followup_to ? compose->followup_to :
3224 compose->newsgroups ? compose->newsgroups : "",
3225 COMPOSE_NEWSGROUPS, PREF_NONE);
3227 compose_reply_set_subject(compose, msginfo);
3229 if (to_ml && compose->ml_post) return;
3230 if (!to_all) return;
3232 if (compose->replyto) {
3233 Xstrdup_a(replyto, compose->replyto, return);
3234 extract_address(replyto);
3236 if (msginfo->from) {
3237 Xstrdup_a(from, msginfo->from, return);
3238 extract_address(from);
3241 if (replyto && from)
3242 cc_list = address_list_append_with_comments(cc_list, from);
3243 if (to_all && msginfo->folder &&
3244 msginfo->folder->prefs->enable_default_reply_to)
3245 cc_list = address_list_append_with_comments(cc_list,
3246 msginfo->folder->prefs->default_reply_to);
3247 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3248 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3250 ac_email = g_utf8_strdown(compose->account->address, -1);
3253 for (cur = cc_list; cur != NULL; cur = cur->next) {
3254 gchar *addr = g_utf8_strdown(cur->data, -1);
3255 extract_address(addr);
3257 if (strcmp(ac_email, addr))
3258 compose_entry_append(compose, (gchar *)cur->data,
3259 COMPOSE_CC, PREF_NONE);
3261 debug_print("Cc address same as compose account's, ignoring\n");
3266 slist_free_strings(cc_list);
3267 g_slist_free(cc_list);
3273 #define SET_ENTRY(entry, str) \
3276 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3279 #define SET_ADDRESS(type, str) \
3282 compose_entry_append(compose, str, type, PREF_NONE); \
3285 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3287 cm_return_if_fail(msginfo != NULL);
3289 SET_ENTRY(subject_entry, msginfo->subject);
3290 SET_ENTRY(from_name, msginfo->from);
3291 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3292 SET_ADDRESS(COMPOSE_CC, compose->cc);
3293 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3294 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3295 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3296 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3298 compose_update_priority_menu_item(compose);
3299 compose_update_privacy_system_menu_item(compose, FALSE);
3300 compose_show_first_last_header(compose, TRUE);
3306 static void compose_insert_sig(Compose *compose, gboolean replace)
3308 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3309 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3311 GtkTextIter iter, iter_end;
3312 gint cur_pos, ins_pos;
3313 gboolean prev_autowrap;
3314 gboolean found = FALSE;
3315 gboolean exists = FALSE;
3317 cm_return_if_fail(compose->account != NULL);
3321 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3322 G_CALLBACK(compose_changed_cb),
3325 mark = gtk_text_buffer_get_insert(buffer);
3326 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3327 cur_pos = gtk_text_iter_get_offset (&iter);
3330 gtk_text_buffer_get_end_iter(buffer, &iter);
3332 exists = (compose->sig_str != NULL);
3335 GtkTextIter first_iter, start_iter, end_iter;
3337 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3339 if (!exists || compose->sig_str[0] == '\0')
3342 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3343 compose->signature_tag);
3346 /* include previous \n\n */
3347 gtk_text_iter_backward_chars(&first_iter, 1);
3348 start_iter = first_iter;
3349 end_iter = first_iter;
3351 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3352 compose->signature_tag);
3353 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3354 compose->signature_tag);
3356 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3362 g_free(compose->sig_str);
3363 compose->sig_str = account_get_signature_str(compose->account);
3365 cur_pos = gtk_text_iter_get_offset(&iter);
3367 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3368 g_free(compose->sig_str);
3369 compose->sig_str = NULL;
3371 if (compose->sig_inserted == FALSE)
3372 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3373 compose->sig_inserted = TRUE;
3375 cur_pos = gtk_text_iter_get_offset(&iter);
3376 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3378 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3379 gtk_text_iter_forward_chars(&iter, 1);
3380 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3381 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3383 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3384 cur_pos = gtk_text_buffer_get_char_count (buffer);
3387 /* put the cursor where it should be
3388 * either where the quote_fmt says, either where it was */
3389 if (compose->set_cursor_pos < 0)
3390 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3392 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3393 compose->set_cursor_pos);
3395 compose->set_cursor_pos = -1;
3396 gtk_text_buffer_place_cursor(buffer, &iter);
3397 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3398 G_CALLBACK(compose_changed_cb),
3404 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3407 GtkTextBuffer *buffer;
3410 const gchar *cur_encoding;
3411 gchar buf[BUFFSIZE];
3414 gboolean prev_autowrap;
3415 gboolean badtxt = FALSE;
3416 struct stat file_stat;
3419 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3421 /* get the size of the file we are about to insert */
3422 ret = g_stat(file, &file_stat);
3424 gchar *shortfile = g_path_get_basename(file);
3425 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3427 return COMPOSE_INSERT_NO_FILE;
3428 } else if (prefs_common.warn_large_insert == TRUE) {
3430 /* ask user for confirmation if the file is large */
3431 if (prefs_common.warn_large_insert_size < 0 ||
3432 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3436 msg = g_strdup_printf(_("You are about to insert a file of %s "
3437 "in the message body. Are you sure you want to do that?"),
3438 to_human_readable(file_stat.st_size));
3439 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3440 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3443 /* do we ask for confirmation next time? */
3444 if (aval & G_ALERTDISABLE) {
3445 /* no confirmation next time, disable feature in preferences */
3446 aval &= ~G_ALERTDISABLE;
3447 prefs_common.warn_large_insert = FALSE;
3450 /* abort file insertion if user canceled action */
3451 if (aval != G_ALERTALTERNATE) {
3452 return COMPOSE_INSERT_NO_FILE;
3458 if ((fp = g_fopen(file, "rb")) == NULL) {
3459 FILE_OP_ERROR(file, "fopen");
3460 return COMPOSE_INSERT_READ_ERROR;
3463 prev_autowrap = compose->autowrap;
3464 compose->autowrap = FALSE;
3466 text = GTK_TEXT_VIEW(compose->text);
3467 buffer = gtk_text_view_get_buffer(text);
3468 mark = gtk_text_buffer_get_insert(buffer);
3469 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3471 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3472 G_CALLBACK(text_inserted),
3475 cur_encoding = conv_get_locale_charset_str_no_utf8();
3477 while (fgets(buf, sizeof(buf), fp) != NULL) {
3480 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3481 str = g_strdup(buf);
3483 str = conv_codeset_strdup
3484 (buf, cur_encoding, CS_INTERNAL);
3487 /* strip <CR> if DOS/Windows file,
3488 replace <CR> with <LF> if Macintosh file. */
3491 if (len > 0 && str[len - 1] != '\n') {
3493 if (str[len] == '\r') str[len] = '\n';
3496 gtk_text_buffer_insert(buffer, &iter, str, -1);
3500 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3501 G_CALLBACK(text_inserted),
3503 compose->autowrap = prev_autowrap;
3504 if (compose->autowrap)
3505 compose_wrap_all(compose);
3510 return COMPOSE_INSERT_INVALID_CHARACTER;
3512 return COMPOSE_INSERT_SUCCESS;
3515 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3516 const gchar *filename,
3517 const gchar *content_type)
3525 GtkListStore *store;
3527 gboolean has_binary = FALSE;
3529 if (!is_file_exist(file)) {
3530 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3531 gboolean result = FALSE;
3532 if (file_from_uri && is_file_exist(file_from_uri)) {
3533 result = compose_attach_append(
3534 compose, file_from_uri,
3538 g_free(file_from_uri);
3541 alertpanel_error("File %s doesn't exist\n", filename);
3544 if ((size = get_file_size(file)) < 0) {
3545 alertpanel_error("Can't get file size of %s\n", filename);
3549 alertpanel_error(_("File %s is empty."), filename);
3552 if ((fp = g_fopen(file, "rb")) == NULL) {
3553 alertpanel_error(_("Can't read %s."), filename);
3558 ainfo = g_new0(AttachInfo, 1);
3559 auto_ainfo = g_auto_pointer_new_with_free
3560 (ainfo, (GFreeFunc) compose_attach_info_free);
3561 ainfo->file = g_strdup(file);
3564 ainfo->content_type = g_strdup(content_type);
3565 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3567 MsgFlags flags = {0, 0};
3569 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3570 ainfo->encoding = ENC_7BIT;
3572 ainfo->encoding = ENC_8BIT;
3574 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3575 if (msginfo && msginfo->subject)
3576 name = g_strdup(msginfo->subject);
3578 name = g_path_get_basename(filename ? filename : file);
3580 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3582 procmsg_msginfo_free(msginfo);
3584 if (!g_ascii_strncasecmp(content_type, "text", 4))
3585 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3587 ainfo->encoding = ENC_BASE64;
3588 name = g_path_get_basename(filename ? filename : file);
3589 ainfo->name = g_strdup(name);
3593 ainfo->content_type = procmime_get_mime_type(file);
3594 if (!ainfo->content_type) {
3595 ainfo->content_type =
3596 g_strdup("application/octet-stream");
3597 ainfo->encoding = ENC_BASE64;
3598 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3600 procmime_get_encoding_for_text_file(file, &has_binary);
3602 ainfo->encoding = ENC_BASE64;
3603 name = g_path_get_basename(filename ? filename : file);
3604 ainfo->name = g_strdup(name);
3608 if (ainfo->name != NULL
3609 && !strcmp(ainfo->name, ".")) {
3610 g_free(ainfo->name);
3614 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3615 g_free(ainfo->content_type);
3616 ainfo->content_type = g_strdup("application/octet-stream");
3619 ainfo->size = (goffset)size;
3620 size_text = to_human_readable((goffset)size);
3622 store = GTK_LIST_STORE(gtk_tree_view_get_model
3623 (GTK_TREE_VIEW(compose->attach_clist)));
3625 gtk_list_store_append(store, &iter);
3626 gtk_list_store_set(store, &iter,
3627 COL_MIMETYPE, ainfo->content_type,
3628 COL_SIZE, size_text,
3629 COL_NAME, ainfo->name,
3631 COL_AUTODATA, auto_ainfo,
3634 g_auto_pointer_free(auto_ainfo);
3635 compose_attach_update_label(compose);
3639 static void compose_use_signing(Compose *compose, gboolean use_signing)
3641 compose->use_signing = use_signing;
3642 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3645 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3647 compose->use_encryption = use_encryption;
3648 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3651 #define NEXT_PART_NOT_CHILD(info) \
3653 node = info->node; \
3654 while (node->children) \
3655 node = g_node_last_child(node); \
3656 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3659 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3663 MimeInfo *firsttext = NULL;
3664 MimeInfo *encrypted = NULL;
3667 const gchar *partname = NULL;
3669 mimeinfo = procmime_scan_message(msginfo);
3670 if (!mimeinfo) return;
3672 if (mimeinfo->node->children == NULL) {
3673 procmime_mimeinfo_free_all(mimeinfo);
3677 /* find first content part */
3678 child = (MimeInfo *) mimeinfo->node->children->data;
3679 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3680 child = (MimeInfo *)child->node->children->data;
3683 if (child->type == MIMETYPE_TEXT) {
3685 debug_print("First text part found\n");
3686 } else if (compose->mode == COMPOSE_REEDIT &&
3687 child->type == MIMETYPE_APPLICATION &&
3688 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3689 encrypted = (MimeInfo *)child->node->parent->data;
3692 child = (MimeInfo *) mimeinfo->node->children->data;
3693 while (child != NULL) {
3696 if (child == encrypted) {
3697 /* skip this part of tree */
3698 NEXT_PART_NOT_CHILD(child);
3702 if (child->type == MIMETYPE_MULTIPART) {
3703 /* get the actual content */
3704 child = procmime_mimeinfo_next(child);
3708 if (child == firsttext) {
3709 child = procmime_mimeinfo_next(child);
3713 outfile = procmime_get_tmp_file_name(child);
3714 if ((err = procmime_get_part(outfile, child)) < 0)
3715 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3717 gchar *content_type;
3719 content_type = procmime_get_content_type_str(child->type, child->subtype);
3721 /* if we meet a pgp signature, we don't attach it, but
3722 * we force signing. */
3723 if ((strcmp(content_type, "application/pgp-signature") &&
3724 strcmp(content_type, "application/pkcs7-signature") &&
3725 strcmp(content_type, "application/x-pkcs7-signature"))
3726 || compose->mode == COMPOSE_REDIRECT) {
3727 partname = procmime_mimeinfo_get_parameter(child, "filename");
3728 if (partname == NULL)
3729 partname = procmime_mimeinfo_get_parameter(child, "name");
3730 if (partname == NULL)
3732 compose_attach_append(compose, outfile,
3733 partname, content_type);
3735 compose_force_signing(compose, compose->account, NULL);
3737 g_free(content_type);
3740 NEXT_PART_NOT_CHILD(child);
3742 procmime_mimeinfo_free_all(mimeinfo);
3745 #undef NEXT_PART_NOT_CHILD
3750 WAIT_FOR_INDENT_CHAR,
3751 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3754 /* return indent length, we allow:
3755 indent characters followed by indent characters or spaces/tabs,
3756 alphabets and numbers immediately followed by indent characters,
3757 and the repeating sequences of the above
3758 If quote ends with multiple spaces, only the first one is included. */
3759 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3760 const GtkTextIter *start, gint *len)
3762 GtkTextIter iter = *start;
3766 IndentState state = WAIT_FOR_INDENT_CHAR;
3769 gint alnum_count = 0;
3770 gint space_count = 0;
3773 if (prefs_common.quote_chars == NULL) {
3777 while (!gtk_text_iter_ends_line(&iter)) {
3778 wc = gtk_text_iter_get_char(&iter);
3779 if (g_unichar_iswide(wc))
3781 clen = g_unichar_to_utf8(wc, ch);
3785 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3786 is_space = g_unichar_isspace(wc);
3788 if (state == WAIT_FOR_INDENT_CHAR) {
3789 if (!is_indent && !g_unichar_isalnum(wc))
3792 quote_len += alnum_count + space_count + 1;
3793 alnum_count = space_count = 0;
3794 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3797 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3798 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3802 else if (is_indent) {
3803 quote_len += alnum_count + space_count + 1;
3804 alnum_count = space_count = 0;
3807 state = WAIT_FOR_INDENT_CHAR;
3811 gtk_text_iter_forward_char(&iter);
3814 if (quote_len > 0 && space_count > 0)
3820 if (quote_len > 0) {
3822 gtk_text_iter_forward_chars(&iter, quote_len);
3823 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3829 /* return >0 if the line is itemized */
3830 static int compose_itemized_length(GtkTextBuffer *buffer,
3831 const GtkTextIter *start)
3833 GtkTextIter iter = *start;
3838 if (gtk_text_iter_ends_line(&iter))
3843 wc = gtk_text_iter_get_char(&iter);
3844 if (!g_unichar_isspace(wc))
3846 gtk_text_iter_forward_char(&iter);
3847 if (gtk_text_iter_ends_line(&iter))
3851 clen = g_unichar_to_utf8(wc, ch);
3855 if (!strchr("*-+", ch[0]))
3858 gtk_text_iter_forward_char(&iter);
3859 if (gtk_text_iter_ends_line(&iter))
3861 wc = gtk_text_iter_get_char(&iter);
3862 if (g_unichar_isspace(wc)) {
3868 /* return the string at the start of the itemization */
3869 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3870 const GtkTextIter *start)
3872 GtkTextIter iter = *start;
3875 GString *item_chars = g_string_new("");
3878 if (gtk_text_iter_ends_line(&iter))
3883 wc = gtk_text_iter_get_char(&iter);
3884 if (!g_unichar_isspace(wc))
3886 gtk_text_iter_forward_char(&iter);
3887 if (gtk_text_iter_ends_line(&iter))
3889 g_string_append_unichar(item_chars, wc);
3892 str = item_chars->str;
3893 g_string_free(item_chars, FALSE);
3897 /* return the number of spaces at a line's start */
3898 static int compose_left_offset_length(GtkTextBuffer *buffer,
3899 const GtkTextIter *start)
3901 GtkTextIter iter = *start;
3904 if (gtk_text_iter_ends_line(&iter))
3908 wc = gtk_text_iter_get_char(&iter);
3909 if (!g_unichar_isspace(wc))
3912 gtk_text_iter_forward_char(&iter);
3913 if (gtk_text_iter_ends_line(&iter))
3917 gtk_text_iter_forward_char(&iter);
3918 if (gtk_text_iter_ends_line(&iter))
3923 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3924 const GtkTextIter *start,
3925 GtkTextIter *break_pos,
3929 GtkTextIter iter = *start, line_end = *start;
3930 PangoLogAttr *attrs;
3937 gboolean can_break = FALSE;
3938 gboolean do_break = FALSE;
3939 gboolean was_white = FALSE;
3940 gboolean prev_dont_break = FALSE;
3942 gtk_text_iter_forward_to_line_end(&line_end);
3943 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3944 len = g_utf8_strlen(str, -1);
3948 g_warning("compose_get_line_break_pos: len = 0!\n");
3952 /* g_print("breaking line: %d: %s (len = %d)\n",
3953 gtk_text_iter_get_line(&iter), str, len); */
3955 attrs = g_new(PangoLogAttr, len + 1);
3957 pango_default_break(str, -1, NULL, attrs, len + 1);
3961 /* skip quote and leading spaces */
3962 for (i = 0; *p != '\0' && i < len; i++) {
3965 wc = g_utf8_get_char(p);
3966 if (i >= quote_len && !g_unichar_isspace(wc))
3968 if (g_unichar_iswide(wc))
3970 else if (*p == '\t')
3974 p = g_utf8_next_char(p);
3977 for (; *p != '\0' && i < len; i++) {
3978 PangoLogAttr *attr = attrs + i;
3982 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3985 was_white = attr->is_white;
3987 /* don't wrap URI */
3988 if ((uri_len = get_uri_len(p)) > 0) {
3990 if (pos > 0 && col > max_col) {
4000 wc = g_utf8_get_char(p);
4001 if (g_unichar_iswide(wc)) {
4003 if (prev_dont_break && can_break && attr->is_line_break)
4005 } else if (*p == '\t')
4009 if (pos > 0 && col > max_col) {
4014 if (*p == '-' || *p == '/')
4015 prev_dont_break = TRUE;
4017 prev_dont_break = FALSE;
4019 p = g_utf8_next_char(p);
4023 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4028 *break_pos = *start;
4029 gtk_text_iter_set_line_offset(break_pos, pos);
4034 static gboolean compose_join_next_line(Compose *compose,
4035 GtkTextBuffer *buffer,
4037 const gchar *quote_str)
4039 GtkTextIter iter_ = *iter, cur, prev, next, end;
4040 PangoLogAttr attrs[3];
4042 gchar *next_quote_str;
4045 gboolean keep_cursor = FALSE;
4047 if (!gtk_text_iter_forward_line(&iter_) ||
4048 gtk_text_iter_ends_line(&iter_)) {
4051 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4053 if ((quote_str || next_quote_str) &&
4054 strcmp2(quote_str, next_quote_str) != 0) {
4055 g_free(next_quote_str);
4058 g_free(next_quote_str);
4061 if (quote_len > 0) {
4062 gtk_text_iter_forward_chars(&end, quote_len);
4063 if (gtk_text_iter_ends_line(&end)) {
4068 /* don't join itemized lines */
4069 if (compose_itemized_length(buffer, &end) > 0) {
4073 /* don't join signature separator */
4074 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4077 /* delete quote str */
4079 gtk_text_buffer_delete(buffer, &iter_, &end);
4081 /* don't join line breaks put by the user */
4083 gtk_text_iter_backward_char(&cur);
4084 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4085 gtk_text_iter_forward_char(&cur);
4089 gtk_text_iter_forward_char(&cur);
4090 /* delete linebreak and extra spaces */
4091 while (gtk_text_iter_backward_char(&cur)) {
4092 wc1 = gtk_text_iter_get_char(&cur);
4093 if (!g_unichar_isspace(wc1))
4098 while (!gtk_text_iter_ends_line(&cur)) {
4099 wc1 = gtk_text_iter_get_char(&cur);
4100 if (!g_unichar_isspace(wc1))
4102 gtk_text_iter_forward_char(&cur);
4105 if (!gtk_text_iter_equal(&prev, &next)) {
4108 mark = gtk_text_buffer_get_insert(buffer);
4109 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4110 if (gtk_text_iter_equal(&prev, &cur))
4112 gtk_text_buffer_delete(buffer, &prev, &next);
4116 /* insert space if required */
4117 gtk_text_iter_backward_char(&prev);
4118 wc1 = gtk_text_iter_get_char(&prev);
4119 wc2 = gtk_text_iter_get_char(&next);
4120 gtk_text_iter_forward_char(&next);
4121 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4122 pango_default_break(str, -1, NULL, attrs, 3);
4123 if (!attrs[1].is_line_break ||
4124 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4125 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4127 gtk_text_iter_backward_char(&iter_);
4128 gtk_text_buffer_place_cursor(buffer, &iter_);
4137 #define ADD_TXT_POS(bp_, ep_, pti_) \
4138 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4139 last = last->next; \
4140 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4141 last->next = NULL; \
4143 g_warning("alloc error scanning URIs\n"); \
4146 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4148 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4149 GtkTextBuffer *buffer;
4150 GtkTextIter iter, break_pos, end_of_line;
4151 gchar *quote_str = NULL;
4153 gboolean wrap_quote = prefs_common.linewrap_quote;
4154 gboolean prev_autowrap = compose->autowrap;
4155 gint startq_offset = -1, noq_offset = -1;
4156 gint uri_start = -1, uri_stop = -1;
4157 gint nouri_start = -1, nouri_stop = -1;
4158 gint num_blocks = 0;
4159 gint quotelevel = -1;
4160 gboolean modified = force;
4161 gboolean removed = FALSE;
4162 gboolean modified_before_remove = FALSE;
4164 gboolean start = TRUE;
4165 gint itemized_len = 0, rem_item_len = 0;
4166 gchar *itemized_chars = NULL;
4167 gboolean item_continuation = FALSE;
4172 if (compose->draft_timeout_tag == -2) {
4176 compose->autowrap = FALSE;
4178 buffer = gtk_text_view_get_buffer(text);
4179 undo_wrapping(compose->undostruct, TRUE);
4184 mark = gtk_text_buffer_get_insert(buffer);
4185 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4189 if (compose->draft_timeout_tag == -2) {
4190 if (gtk_text_iter_ends_line(&iter)) {
4191 while (gtk_text_iter_ends_line(&iter) &&
4192 gtk_text_iter_forward_line(&iter))
4195 while (gtk_text_iter_backward_line(&iter)) {
4196 if (gtk_text_iter_ends_line(&iter)) {
4197 gtk_text_iter_forward_line(&iter);
4203 /* move to line start */
4204 gtk_text_iter_set_line_offset(&iter, 0);
4207 itemized_len = compose_itemized_length(buffer, &iter);
4209 if (!itemized_len) {
4210 itemized_len = compose_left_offset_length(buffer, &iter);
4211 item_continuation = TRUE;
4215 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4217 /* go until paragraph end (empty line) */
4218 while (start || !gtk_text_iter_ends_line(&iter)) {
4219 gchar *scanpos = NULL;
4220 /* parse table - in order of priority */
4222 const gchar *needle; /* token */
4224 /* token search function */
4225 gchar *(*search) (const gchar *haystack,
4226 const gchar *needle);
4227 /* part parsing function */
4228 gboolean (*parse) (const gchar *start,
4229 const gchar *scanpos,
4233 /* part to URI function */
4234 gchar *(*build_uri) (const gchar *bp,
4238 static struct table parser[] = {
4239 {"http://", strcasestr, get_uri_part, make_uri_string},
4240 {"https://", strcasestr, get_uri_part, make_uri_string},
4241 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4242 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4243 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4244 {"www.", strcasestr, get_uri_part, make_http_string},
4245 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4246 {"@", strcasestr, get_email_part, make_email_string}
4248 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4249 gint last_index = PARSE_ELEMS;
4251 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4255 if (!prev_autowrap && num_blocks == 0) {
4257 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4258 G_CALLBACK(text_inserted),
4261 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4264 uri_start = uri_stop = -1;
4266 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4269 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4270 if (startq_offset == -1)
4271 startq_offset = gtk_text_iter_get_offset(&iter);
4272 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4273 if (quotelevel > 2) {
4274 /* recycle colors */
4275 if (prefs_common.recycle_quote_colors)
4284 if (startq_offset == -1)
4285 noq_offset = gtk_text_iter_get_offset(&iter);
4289 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4292 if (gtk_text_iter_ends_line(&iter)) {
4294 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4295 prefs_common.linewrap_len,
4297 GtkTextIter prev, next, cur;
4298 if (prev_autowrap != FALSE || force) {
4299 compose->automatic_break = TRUE;
4301 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4302 compose->automatic_break = FALSE;
4303 if (itemized_len && compose->autoindent) {
4304 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4305 if (!item_continuation)
4306 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4308 } else if (quote_str && wrap_quote) {
4309 compose->automatic_break = TRUE;
4311 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4312 compose->automatic_break = FALSE;
4313 if (itemized_len && compose->autoindent) {
4314 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4315 if (!item_continuation)
4316 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4320 /* remove trailing spaces */
4322 rem_item_len = itemized_len;
4323 while (compose->autoindent && rem_item_len-- > 0)
4324 gtk_text_iter_backward_char(&cur);
4325 gtk_text_iter_backward_char(&cur);
4328 while (!gtk_text_iter_starts_line(&cur)) {
4331 gtk_text_iter_backward_char(&cur);
4332 wc = gtk_text_iter_get_char(&cur);
4333 if (!g_unichar_isspace(wc))
4337 if (!gtk_text_iter_equal(&prev, &next)) {
4338 gtk_text_buffer_delete(buffer, &prev, &next);
4340 gtk_text_iter_forward_char(&break_pos);
4344 gtk_text_buffer_insert(buffer, &break_pos,
4348 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4350 /* move iter to current line start */
4351 gtk_text_iter_set_line_offset(&iter, 0);
4358 /* move iter to next line start */
4364 if (!prev_autowrap && num_blocks > 0) {
4366 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4367 G_CALLBACK(text_inserted),
4371 while (!gtk_text_iter_ends_line(&end_of_line)) {
4372 gtk_text_iter_forward_char(&end_of_line);
4374 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4376 nouri_start = gtk_text_iter_get_offset(&iter);
4377 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4379 walk_pos = gtk_text_iter_get_offset(&iter);
4380 /* FIXME: this looks phony. scanning for anything in the parse table */
4381 for (n = 0; n < PARSE_ELEMS; n++) {
4384 tmp = parser[n].search(walk, parser[n].needle);
4386 if (scanpos == NULL || tmp < scanpos) {
4395 /* check if URI can be parsed */
4396 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4397 (const gchar **)&ep, FALSE)
4398 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4402 strlen(parser[last_index].needle);
4405 uri_start = walk_pos + (bp - o_walk);
4406 uri_stop = walk_pos + (ep - o_walk);
4410 gtk_text_iter_forward_line(&iter);
4413 if (startq_offset != -1) {
4414 GtkTextIter startquote, endquote;
4415 gtk_text_buffer_get_iter_at_offset(
4416 buffer, &startquote, startq_offset);
4419 switch (quotelevel) {
4421 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4422 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4423 gtk_text_buffer_apply_tag_by_name(
4424 buffer, "quote0", &startquote, &endquote);
4425 gtk_text_buffer_remove_tag_by_name(
4426 buffer, "quote1", &startquote, &endquote);
4427 gtk_text_buffer_remove_tag_by_name(
4428 buffer, "quote2", &startquote, &endquote);
4433 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4434 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4435 gtk_text_buffer_apply_tag_by_name(
4436 buffer, "quote1", &startquote, &endquote);
4437 gtk_text_buffer_remove_tag_by_name(
4438 buffer, "quote0", &startquote, &endquote);
4439 gtk_text_buffer_remove_tag_by_name(
4440 buffer, "quote2", &startquote, &endquote);
4445 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4446 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4447 gtk_text_buffer_apply_tag_by_name(
4448 buffer, "quote2", &startquote, &endquote);
4449 gtk_text_buffer_remove_tag_by_name(
4450 buffer, "quote0", &startquote, &endquote);
4451 gtk_text_buffer_remove_tag_by_name(
4452 buffer, "quote1", &startquote, &endquote);
4458 } else if (noq_offset != -1) {
4459 GtkTextIter startnoquote, endnoquote;
4460 gtk_text_buffer_get_iter_at_offset(
4461 buffer, &startnoquote, noq_offset);
4464 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4465 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4466 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4467 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4468 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4469 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4470 gtk_text_buffer_remove_tag_by_name(
4471 buffer, "quote0", &startnoquote, &endnoquote);
4472 gtk_text_buffer_remove_tag_by_name(
4473 buffer, "quote1", &startnoquote, &endnoquote);
4474 gtk_text_buffer_remove_tag_by_name(
4475 buffer, "quote2", &startnoquote, &endnoquote);
4481 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4482 GtkTextIter nouri_start_iter, nouri_end_iter;
4483 gtk_text_buffer_get_iter_at_offset(
4484 buffer, &nouri_start_iter, nouri_start);
4485 gtk_text_buffer_get_iter_at_offset(
4486 buffer, &nouri_end_iter, nouri_stop);
4487 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4488 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4489 gtk_text_buffer_remove_tag_by_name(
4490 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4491 modified_before_remove = modified;
4496 if (uri_start >= 0 && uri_stop > 0) {
4497 GtkTextIter uri_start_iter, uri_end_iter, back;
4498 gtk_text_buffer_get_iter_at_offset(
4499 buffer, &uri_start_iter, uri_start);
4500 gtk_text_buffer_get_iter_at_offset(
4501 buffer, &uri_end_iter, uri_stop);
4502 back = uri_end_iter;
4503 gtk_text_iter_backward_char(&back);
4504 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4505 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4506 gtk_text_buffer_apply_tag_by_name(
4507 buffer, "link", &uri_start_iter, &uri_end_iter);
4509 if (removed && !modified_before_remove) {
4515 // debug_print("not modified, out after %d lines\n", lines);
4519 // debug_print("modified, out after %d lines\n", lines);
4521 g_free(itemized_chars);
4524 undo_wrapping(compose->undostruct, FALSE);
4525 compose->autowrap = prev_autowrap;
4530 void compose_action_cb(void *data)
4532 Compose *compose = (Compose *)data;
4533 compose_wrap_all(compose);
4536 static void compose_wrap_all(Compose *compose)
4538 compose_wrap_all_full(compose, FALSE);
4541 static void compose_wrap_all_full(Compose *compose, gboolean force)
4543 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4544 GtkTextBuffer *buffer;
4546 gboolean modified = TRUE;
4548 buffer = gtk_text_view_get_buffer(text);
4550 gtk_text_buffer_get_start_iter(buffer, &iter);
4551 while (!gtk_text_iter_is_end(&iter) && modified)
4552 modified = compose_beautify_paragraph(compose, &iter, force);
4556 static void compose_set_title(Compose *compose)
4562 edited = compose->modified ? _(" [Edited]") : "";
4564 subject = gtk_editable_get_chars(
4565 GTK_EDITABLE(compose->subject_entry), 0, -1);
4567 #ifndef GENERIC_UMPC
4568 if (subject && strlen(subject))
4569 str = g_strdup_printf(_("%s - Compose message%s"),
4572 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4574 str = g_strdup(_("Compose message"));
4577 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4583 * compose_current_mail_account:
4585 * Find a current mail account (the currently selected account, or the
4586 * default account, if a news account is currently selected). If a
4587 * mail account cannot be found, display an error message.
4589 * Return value: Mail account, or NULL if not found.
4591 static PrefsAccount *
4592 compose_current_mail_account(void)
4596 if (cur_account && cur_account->protocol != A_NNTP)
4599 ac = account_get_default();
4600 if (!ac || ac->protocol == A_NNTP) {
4601 alertpanel_error(_("Account for sending mail is not specified.\n"
4602 "Please select a mail account before sending."));
4609 #define QUOTE_IF_REQUIRED(out, str) \
4611 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4615 len = strlen(str) + 3; \
4616 if ((__tmp = alloca(len)) == NULL) { \
4617 g_warning("can't allocate memory\n"); \
4618 g_string_free(header, TRUE); \
4621 g_snprintf(__tmp, len, "\"%s\"", str); \
4626 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4627 g_warning("can't allocate memory\n"); \
4628 g_string_free(header, TRUE); \
4631 strcpy(__tmp, str); \
4637 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4639 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4643 len = strlen(str) + 3; \
4644 if ((__tmp = alloca(len)) == NULL) { \
4645 g_warning("can't allocate memory\n"); \
4648 g_snprintf(__tmp, len, "\"%s\"", str); \
4653 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4654 g_warning("can't allocate memory\n"); \
4657 strcpy(__tmp, str); \
4663 static void compose_select_account(Compose *compose, PrefsAccount *account,
4666 gchar *from = NULL, *header;
4667 ComposeHeaderEntry *header_entry;
4669 cm_return_if_fail(account != NULL);
4671 compose->account = account;
4672 if (account->name && *account->name) {
4674 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4675 from = g_strdup_printf("%s <%s>",
4676 buf, account->address);
4677 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4679 from = g_strdup_printf("<%s>",
4681 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4686 compose_set_title(compose);
4688 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4689 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4691 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4692 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4693 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4695 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4697 activate_privacy_system(compose, account, FALSE);
4699 if (!init && compose->mode != COMPOSE_REDIRECT) {
4700 undo_block(compose->undostruct);
4701 compose_insert_sig(compose, TRUE);
4702 undo_unblock(compose->undostruct);
4705 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4706 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4708 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4709 if (account->protocol == A_NNTP) {
4710 if (!strcmp(header, _("To:")))
4711 combobox_select_by_text(
4712 GTK_COMBO_BOX(header_entry->combo),
4715 if (!strcmp(header, _("Newsgroups:")))
4716 combobox_select_by_text(
4717 GTK_COMBO_BOX(header_entry->combo),
4725 /* use account's dict info if set */
4726 if (compose->gtkaspell) {
4727 if (account->enable_default_dictionary)
4728 gtkaspell_change_dict(compose->gtkaspell,
4729 account->default_dictionary, FALSE);
4730 if (account->enable_default_alt_dictionary)
4731 gtkaspell_change_alt_dict(compose->gtkaspell,
4732 account->default_alt_dictionary);
4733 if (account->enable_default_dictionary
4734 || account->enable_default_alt_dictionary)
4735 compose_spell_menu_changed(compose);
4740 gboolean compose_check_for_valid_recipient(Compose *compose) {
4741 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4742 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4743 gboolean recipient_found = FALSE;
4747 /* free to and newsgroup list */
4748 slist_free_strings(compose->to_list);
4749 g_slist_free(compose->to_list);
4750 compose->to_list = NULL;
4752 slist_free_strings(compose->newsgroup_list);
4753 g_slist_free(compose->newsgroup_list);
4754 compose->newsgroup_list = NULL;
4756 /* search header entries for to and newsgroup entries */
4757 for (list = compose->header_list; list; list = list->next) {
4760 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4761 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4764 if (entry[0] != '\0') {
4765 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4766 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4767 compose->to_list = address_list_append(compose->to_list, entry);
4768 recipient_found = TRUE;
4771 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4772 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4773 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4774 recipient_found = TRUE;
4781 return recipient_found;
4784 static gboolean compose_check_for_set_recipients(Compose *compose)
4786 if (compose->account->set_autocc && compose->account->auto_cc) {
4787 gboolean found_other = FALSE;
4789 /* search header entries for to and newsgroup entries */
4790 for (list = compose->header_list; list; list = list->next) {
4793 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4794 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4797 if (strcmp(entry, compose->account->auto_cc)
4798 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4808 if (compose->batch) {
4809 gtk_widget_show_all(compose->window);
4811 aval = alertpanel(_("Send"),
4812 _("The only recipient is the default CC address. Send anyway?"),
4813 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4814 if (aval != G_ALERTALTERNATE)
4818 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4819 gboolean found_other = FALSE;
4821 /* search header entries for to and newsgroup entries */
4822 for (list = compose->header_list; list; list = list->next) {
4825 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4826 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4829 if (strcmp(entry, compose->account->auto_bcc)
4830 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4840 if (compose->batch) {
4841 gtk_widget_show_all(compose->window);
4843 aval = alertpanel(_("Send"),
4844 _("The only recipient is the default BCC address. Send anyway?"),
4845 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4846 if (aval != G_ALERTALTERNATE)
4853 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4857 if (compose_check_for_valid_recipient(compose) == FALSE) {
4858 if (compose->batch) {
4859 gtk_widget_show_all(compose->window);
4861 alertpanel_error(_("Recipient is not specified."));
4865 if (compose_check_for_set_recipients(compose) == FALSE) {
4869 if (!compose->batch) {
4870 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4871 if (*str == '\0' && check_everything == TRUE &&
4872 compose->mode != COMPOSE_REDIRECT) {
4874 gchar *button_label;
4877 if (compose->sending)
4878 button_label = _("+_Send");
4880 button_label = _("+_Queue");
4881 message = g_strdup_printf(_("Subject is empty. %s"),
4882 compose->sending?_("Send it anyway?"):
4883 _("Queue it anyway?"));
4885 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4886 GTK_STOCK_CANCEL, button_label, NULL);
4888 if (aval != G_ALERTALTERNATE)
4893 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4899 gint compose_send(Compose *compose)
4902 FolderItem *folder = NULL;
4904 gchar *msgpath = NULL;
4905 gboolean discard_window = FALSE;
4906 gchar *errstr = NULL;
4907 gchar *tmsgid = NULL;
4908 MainWindow *mainwin = mainwindow_get_mainwindow();
4909 gboolean queued_removed = FALSE;
4911 if (prefs_common.send_dialog_invisible
4912 || compose->batch == TRUE)
4913 discard_window = TRUE;
4915 compose_allow_user_actions (compose, FALSE);
4916 compose->sending = TRUE;
4918 if (compose_check_entries(compose, TRUE) == FALSE) {
4919 if (compose->batch) {
4920 gtk_widget_show_all(compose->window);
4926 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4929 if (compose->batch) {
4930 gtk_widget_show_all(compose->window);
4933 alertpanel_error(_("Could not queue message for sending:\n\n"
4934 "Charset conversion failed."));
4935 } else if (val == -5) {
4936 alertpanel_error(_("Could not queue message for sending:\n\n"
4937 "Couldn't get recipient encryption key."));
4938 } else if (val == -6) {
4940 } else if (val == -3) {
4941 if (privacy_peek_error())
4942 alertpanel_error(_("Could not queue message for sending:\n\n"
4943 "Signature failed: %s"), privacy_get_error());
4944 } else if (val == -2 && errno != 0) {
4945 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4947 alertpanel_error(_("Could not queue message for sending."));
4952 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4953 if (discard_window) {
4954 compose->sending = FALSE;
4955 compose_close(compose);
4956 /* No more compose access in the normal codepath
4957 * after this point! */
4962 alertpanel_error(_("The message was queued but could not be "
4963 "sent.\nUse \"Send queued messages\" from "
4964 "the main window to retry."));
4965 if (!discard_window) {
4972 if (msgpath == NULL) {
4973 msgpath = folder_item_fetch_msg(folder, msgnum);
4974 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4977 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4978 claws_unlink(msgpath);
4981 if (!discard_window) {
4983 if (!queued_removed)
4984 folder_item_remove_msg(folder, msgnum);
4985 folder_item_scan(folder);
4987 /* make sure we delete that */
4988 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4990 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4991 folder_item_remove_msg(folder, tmp->msgnum);
4992 procmsg_msginfo_free(tmp);
4999 if (!queued_removed)
5000 folder_item_remove_msg(folder, msgnum);
5001 folder_item_scan(folder);
5003 /* make sure we delete that */
5004 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5006 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5007 folder_item_remove_msg(folder, tmp->msgnum);
5008 procmsg_msginfo_free(tmp);
5011 if (!discard_window) {
5012 compose->sending = FALSE;
5013 compose_allow_user_actions (compose, TRUE);
5014 compose_close(compose);
5018 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5019 "the main window to retry."), errstr);
5022 alertpanel_error_log(_("The message was queued but could not be "
5023 "sent.\nUse \"Send queued messages\" from "
5024 "the main window to retry."));
5026 if (!discard_window) {
5035 toolbar_main_set_sensitive(mainwin);
5036 main_window_set_menu_sensitive(mainwin);
5042 compose_allow_user_actions (compose, TRUE);
5043 compose->sending = FALSE;
5044 compose->modified = TRUE;
5045 toolbar_main_set_sensitive(mainwin);
5046 main_window_set_menu_sensitive(mainwin);
5051 static gboolean compose_use_attach(Compose *compose)
5053 GtkTreeModel *model = gtk_tree_view_get_model
5054 (GTK_TREE_VIEW(compose->attach_clist));
5055 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5058 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5061 gchar buf[BUFFSIZE];
5063 gboolean first_to_address;
5064 gboolean first_cc_address;
5066 ComposeHeaderEntry *headerentry;
5067 const gchar *headerentryname;
5068 const gchar *cc_hdr;
5069 const gchar *to_hdr;
5070 gboolean err = FALSE;
5072 debug_print("Writing redirect header\n");
5074 cc_hdr = prefs_common_translated_header_name("Cc:");
5075 to_hdr = prefs_common_translated_header_name("To:");
5077 first_to_address = TRUE;
5078 for (list = compose->header_list; list; list = list->next) {
5079 headerentry = ((ComposeHeaderEntry *)list->data);
5080 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5082 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5083 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5084 Xstrdup_a(str, entstr, return -1);
5086 if (str[0] != '\0') {
5087 compose_convert_header
5088 (compose, buf, sizeof(buf), str,
5089 strlen("Resent-To") + 2, TRUE);
5091 if (first_to_address) {
5092 err |= (fprintf(fp, "Resent-To: ") < 0);
5093 first_to_address = FALSE;
5095 err |= (fprintf(fp, ",") < 0);
5097 err |= (fprintf(fp, "%s", buf) < 0);
5101 if (!first_to_address) {
5102 err |= (fprintf(fp, "\n") < 0);
5105 first_cc_address = TRUE;
5106 for (list = compose->header_list; list; list = list->next) {
5107 headerentry = ((ComposeHeaderEntry *)list->data);
5108 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5110 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5111 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5112 Xstrdup_a(str, strg, return -1);
5114 if (str[0] != '\0') {
5115 compose_convert_header
5116 (compose, buf, sizeof(buf), str,
5117 strlen("Resent-Cc") + 2, TRUE);
5119 if (first_cc_address) {
5120 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5121 first_cc_address = FALSE;
5123 err |= (fprintf(fp, ",") < 0);
5125 err |= (fprintf(fp, "%s", buf) < 0);
5129 if (!first_cc_address) {
5130 err |= (fprintf(fp, "\n") < 0);
5133 return (err ? -1:0);
5136 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5138 gchar buf[BUFFSIZE];
5140 const gchar *entstr;
5141 /* struct utsname utsbuf; */
5142 gboolean err = FALSE;
5144 cm_return_val_if_fail(fp != NULL, -1);
5145 cm_return_val_if_fail(compose->account != NULL, -1);
5146 cm_return_val_if_fail(compose->account->address != NULL, -1);
5149 get_rfc822_date(buf, sizeof(buf));
5150 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5153 if (compose->account->name && *compose->account->name) {
5154 compose_convert_header
5155 (compose, buf, sizeof(buf), compose->account->name,
5156 strlen("From: "), TRUE);
5157 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5158 buf, compose->account->address) < 0);
5160 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5163 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5164 if (*entstr != '\0') {
5165 Xstrdup_a(str, entstr, return -1);
5168 compose_convert_header(compose, buf, sizeof(buf), str,
5169 strlen("Subject: "), FALSE);
5170 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5174 /* Resent-Message-ID */
5175 if (compose->account->set_domain && compose->account->domain) {
5176 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5177 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5178 g_snprintf(buf, sizeof(buf), "%s",
5179 strchr(compose->account->address, '@') ?
5180 strchr(compose->account->address, '@')+1 :
5181 compose->account->address);
5183 g_snprintf(buf, sizeof(buf), "%s", "");
5186 if (compose->account->gen_msgid) {
5188 if (compose->account->msgid_with_addr) {
5189 addr = compose->account->address;
5191 generate_msgid(buf, sizeof(buf), addr);
5192 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5193 compose->msgid = g_strdup(buf);
5195 compose->msgid = NULL;
5198 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5201 /* separator between header and body */
5202 err |= (fputs("\n", fp) == EOF);
5204 return (err ? -1:0);
5207 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5211 gchar buf[BUFFSIZE];
5213 gboolean skip = FALSE;
5214 gboolean err = FALSE;
5215 gchar *not_included[]={
5216 "Return-Path:", "Delivered-To:", "Received:",
5217 "Subject:", "X-UIDL:", "AF:",
5218 "NF:", "PS:", "SRH:",
5219 "SFN:", "DSR:", "MID:",
5220 "CFG:", "PT:", "S:",
5221 "RQ:", "SSV:", "NSV:",
5222 "SSH:", "R:", "MAID:",
5223 "NAID:", "RMID:", "FMID:",
5224 "SCF:", "RRCPT:", "NG:",
5225 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5226 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5227 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5228 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5229 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5232 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5233 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5237 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5239 for (i = 0; not_included[i] != NULL; i++) {
5240 if (g_ascii_strncasecmp(buf, not_included[i],
5241 strlen(not_included[i])) == 0) {
5248 if (fputs(buf, fdest) == -1)
5251 if (!prefs_common.redirect_keep_from) {
5252 if (g_ascii_strncasecmp(buf, "From:",
5253 strlen("From:")) == 0) {
5254 err |= (fputs(" (by way of ", fdest) == EOF);
5255 if (compose->account->name
5256 && *compose->account->name) {
5257 compose_convert_header
5258 (compose, buf, sizeof(buf),
5259 compose->account->name,
5262 err |= (fprintf(fdest, "%s <%s>",
5264 compose->account->address) < 0);
5266 err |= (fprintf(fdest, "%s",
5267 compose->account->address) < 0);
5268 err |= (fputs(")", fdest) == EOF);
5272 if (fputs("\n", fdest) == -1)
5279 if (compose_redirect_write_headers(compose, fdest))
5282 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5283 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5296 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5298 GtkTextBuffer *buffer;
5299 GtkTextIter start, end;
5302 const gchar *out_codeset;
5303 EncodingType encoding = ENC_UNKNOWN;
5304 MimeInfo *mimemsg, *mimetext;
5306 const gchar *src_codeset = CS_INTERNAL;
5307 gchar *from_addr = NULL;
5308 gchar *from_name = NULL;
5310 if (action == COMPOSE_WRITE_FOR_SEND)
5311 attach_parts = TRUE;
5313 /* create message MimeInfo */
5314 mimemsg = procmime_mimeinfo_new();
5315 mimemsg->type = MIMETYPE_MESSAGE;
5316 mimemsg->subtype = g_strdup("rfc822");
5317 mimemsg->content = MIMECONTENT_MEM;
5318 mimemsg->tmp = TRUE; /* must free content later */
5319 mimemsg->data.mem = compose_get_header(compose);
5321 /* Create text part MimeInfo */
5322 /* get all composed text */
5323 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5324 gtk_text_buffer_get_start_iter(buffer, &start);
5325 gtk_text_buffer_get_end_iter(buffer, &end);
5326 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5328 out_codeset = conv_get_charset_str(compose->out_encoding);
5330 if (!out_codeset && is_ascii_str(chars)) {
5331 out_codeset = CS_US_ASCII;
5332 } else if (prefs_common.outgoing_fallback_to_ascii &&
5333 is_ascii_str(chars)) {
5334 out_codeset = CS_US_ASCII;
5335 encoding = ENC_7BIT;
5339 gchar *test_conv_global_out = NULL;
5340 gchar *test_conv_reply = NULL;
5342 /* automatic mode. be automatic. */
5343 codeconv_set_strict(TRUE);
5345 out_codeset = conv_get_outgoing_charset_str();
5347 debug_print("trying to convert to %s\n", out_codeset);
5348 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5351 if (!test_conv_global_out && compose->orig_charset
5352 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5353 out_codeset = compose->orig_charset;
5354 debug_print("failure; trying to convert to %s\n", out_codeset);
5355 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5358 if (!test_conv_global_out && !test_conv_reply) {
5360 out_codeset = CS_INTERNAL;
5361 debug_print("failure; finally using %s\n", out_codeset);
5363 g_free(test_conv_global_out);
5364 g_free(test_conv_reply);
5365 codeconv_set_strict(FALSE);
5368 if (encoding == ENC_UNKNOWN) {
5369 if (prefs_common.encoding_method == CTE_BASE64)
5370 encoding = ENC_BASE64;
5371 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5372 encoding = ENC_QUOTED_PRINTABLE;
5373 else if (prefs_common.encoding_method == CTE_8BIT)
5374 encoding = ENC_8BIT;
5376 encoding = procmime_get_encoding_for_charset(out_codeset);
5379 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5380 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5382 if (action == COMPOSE_WRITE_FOR_SEND) {
5383 codeconv_set_strict(TRUE);
5384 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5385 codeconv_set_strict(FALSE);
5391 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5392 "to the specified %s charset.\n"
5393 "Send it as %s?"), out_codeset, src_codeset);
5394 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5395 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5398 if (aval != G_ALERTALTERNATE) {
5403 out_codeset = src_codeset;
5409 out_codeset = src_codeset;
5414 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5415 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5416 strstr(buf, "\nFrom ") != NULL) {
5417 encoding = ENC_QUOTED_PRINTABLE;
5421 mimetext = procmime_mimeinfo_new();
5422 mimetext->content = MIMECONTENT_MEM;
5423 mimetext->tmp = TRUE; /* must free content later */
5424 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5425 * and free the data, which we need later. */
5426 mimetext->data.mem = g_strdup(buf);
5427 mimetext->type = MIMETYPE_TEXT;
5428 mimetext->subtype = g_strdup("plain");
5429 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5430 g_strdup(out_codeset));
5432 /* protect trailing spaces when signing message */
5433 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5434 privacy_system_can_sign(compose->privacy_system)) {
5435 encoding = ENC_QUOTED_PRINTABLE;
5438 debug_print("main text: %zd bytes encoded as %s in %d\n",
5439 strlen(buf), out_codeset, encoding);
5441 /* check for line length limit */
5442 if (action == COMPOSE_WRITE_FOR_SEND &&
5443 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5444 check_line_length(buf, 1000, &line) < 0) {
5448 msg = g_strdup_printf
5449 (_("Line %d exceeds the line length limit (998 bytes).\n"
5450 "The contents of the message might be broken on the way to the delivery.\n"
5452 "Send it anyway?"), line + 1);
5453 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5455 if (aval != G_ALERTALTERNATE) {
5461 if (encoding != ENC_UNKNOWN)
5462 procmime_encode_content(mimetext, encoding);
5464 /* append attachment parts */
5465 if (compose_use_attach(compose) && attach_parts) {
5466 MimeInfo *mimempart;
5467 gchar *boundary = NULL;
5468 mimempart = procmime_mimeinfo_new();
5469 mimempart->content = MIMECONTENT_EMPTY;
5470 mimempart->type = MIMETYPE_MULTIPART;
5471 mimempart->subtype = g_strdup("mixed");
5475 boundary = generate_mime_boundary(NULL);
5476 } while (strstr(buf, boundary) != NULL);
5478 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5481 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5483 g_node_append(mimempart->node, mimetext->node);
5484 g_node_append(mimemsg->node, mimempart->node);
5486 if (compose_add_attachments(compose, mimempart) < 0)
5489 g_node_append(mimemsg->node, mimetext->node);
5493 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5494 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5495 /* extract name and address */
5496 if (strstr(spec, " <") && strstr(spec, ">")) {
5497 from_addr = g_strdup(strrchr(spec, '<')+1);
5498 *(strrchr(from_addr, '>')) = '\0';
5499 from_name = g_strdup(spec);
5500 *(strrchr(from_name, '<')) = '\0';
5507 /* sign message if sending */
5508 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5509 privacy_system_can_sign(compose->privacy_system))
5510 if (!privacy_sign(compose->privacy_system, mimemsg,
5511 compose->account, from_addr)) {
5518 procmime_write_mimeinfo(mimemsg, fp);
5520 procmime_mimeinfo_free_all(mimemsg);
5525 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5527 GtkTextBuffer *buffer;
5528 GtkTextIter start, end;
5533 if ((fp = g_fopen(file, "wb")) == NULL) {
5534 FILE_OP_ERROR(file, "fopen");
5538 /* chmod for security */
5539 if (change_file_mode_rw(fp, file) < 0) {
5540 FILE_OP_ERROR(file, "chmod");
5541 g_warning("can't change file mode\n");
5544 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5545 gtk_text_buffer_get_start_iter(buffer, &start);
5546 gtk_text_buffer_get_end_iter(buffer, &end);
5547 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5549 chars = conv_codeset_strdup
5550 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5553 if (!chars) return -1;
5556 len = strlen(chars);
5557 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5558 FILE_OP_ERROR(file, "fwrite");
5567 if (fclose(fp) == EOF) {
5568 FILE_OP_ERROR(file, "fclose");
5575 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5578 MsgInfo *msginfo = compose->targetinfo;
5580 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5581 if (!msginfo) return -1;
5583 if (!force && MSG_IS_LOCKED(msginfo->flags))
5586 item = msginfo->folder;
5587 cm_return_val_if_fail(item != NULL, -1);
5589 if (procmsg_msg_exist(msginfo) &&
5590 (folder_has_parent_of_type(item, F_QUEUE) ||
5591 folder_has_parent_of_type(item, F_DRAFT)
5592 || msginfo == compose->autosaved_draft)) {
5593 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5594 g_warning("can't remove the old message\n");
5597 debug_print("removed reedit target %d\n", msginfo->msgnum);
5604 static void compose_remove_draft(Compose *compose)
5607 MsgInfo *msginfo = compose->targetinfo;
5608 drafts = account_get_special_folder(compose->account, F_DRAFT);
5610 if (procmsg_msg_exist(msginfo)) {
5611 folder_item_remove_msg(drafts, msginfo->msgnum);
5616 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5617 gboolean remove_reedit_target)
5619 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5622 static gboolean compose_warn_encryption(Compose *compose)
5624 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5625 AlertValue val = G_ALERTALTERNATE;
5627 if (warning == NULL)
5630 val = alertpanel_full(_("Encryption warning"), warning,
5631 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5632 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5633 if (val & G_ALERTDISABLE) {
5634 val &= ~G_ALERTDISABLE;
5635 if (val == G_ALERTALTERNATE)
5636 privacy_inhibit_encrypt_warning(compose->privacy_system,
5640 if (val == G_ALERTALTERNATE) {
5647 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5648 gchar **msgpath, gboolean check_subject,
5649 gboolean remove_reedit_target)
5656 static gboolean lock = FALSE;
5657 PrefsAccount *mailac = NULL, *newsac = NULL;
5658 gboolean err = FALSE;
5660 debug_print("queueing message...\n");
5661 cm_return_val_if_fail(compose->account != NULL, -1);
5665 if (compose_check_entries(compose, check_subject) == FALSE) {
5667 if (compose->batch) {
5668 gtk_widget_show_all(compose->window);
5673 if (!compose->to_list && !compose->newsgroup_list) {
5674 g_warning("can't get recipient list.");
5679 if (compose->to_list) {
5680 if (compose->account->protocol != A_NNTP)
5681 mailac = compose->account;
5682 else if (cur_account && cur_account->protocol != A_NNTP)
5683 mailac = cur_account;
5684 else if (!(mailac = compose_current_mail_account())) {
5686 alertpanel_error(_("No account for sending mails available!"));
5691 if (compose->newsgroup_list) {
5692 if (compose->account->protocol == A_NNTP)
5693 newsac = compose->account;
5696 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5701 /* write queue header */
5702 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5703 G_DIR_SEPARATOR, compose, (guint) rand());
5704 debug_print("queuing to %s\n", tmp);
5705 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5706 FILE_OP_ERROR(tmp, "fopen");
5712 if (change_file_mode_rw(fp, tmp) < 0) {
5713 FILE_OP_ERROR(tmp, "chmod");
5714 g_warning("can't change file mode\n");
5717 /* queueing variables */
5718 err |= (fprintf(fp, "AF:\n") < 0);
5719 err |= (fprintf(fp, "NF:0\n") < 0);
5720 err |= (fprintf(fp, "PS:10\n") < 0);
5721 err |= (fprintf(fp, "SRH:1\n") < 0);
5722 err |= (fprintf(fp, "SFN:\n") < 0);
5723 err |= (fprintf(fp, "DSR:\n") < 0);
5725 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5727 err |= (fprintf(fp, "MID:\n") < 0);
5728 err |= (fprintf(fp, "CFG:\n") < 0);
5729 err |= (fprintf(fp, "PT:0\n") < 0);
5730 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5731 err |= (fprintf(fp, "RQ:\n") < 0);
5733 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5735 err |= (fprintf(fp, "SSV:\n") < 0);
5737 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5739 err |= (fprintf(fp, "NSV:\n") < 0);
5740 err |= (fprintf(fp, "SSH:\n") < 0);
5741 /* write recepient list */
5742 if (compose->to_list) {
5743 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5744 for (cur = compose->to_list->next; cur != NULL;
5746 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5747 err |= (fprintf(fp, "\n") < 0);
5749 /* write newsgroup list */
5750 if (compose->newsgroup_list) {
5751 err |= (fprintf(fp, "NG:") < 0);
5752 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5753 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5754 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5755 err |= (fprintf(fp, "\n") < 0);
5757 /* Sylpheed account IDs */
5759 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5761 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5764 if (compose->privacy_system != NULL) {
5765 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5766 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5767 if (compose->use_encryption) {
5769 if (!compose_warn_encryption(compose)) {
5776 if (mailac && mailac->encrypt_to_self) {
5777 GSList *tmp_list = g_slist_copy(compose->to_list);
5778 tmp_list = g_slist_append(tmp_list, compose->account->address);
5779 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5780 g_slist_free(tmp_list);
5782 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5784 if (encdata != NULL) {
5785 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5786 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5787 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5789 } /* else we finally dont want to encrypt */
5791 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5792 /* and if encdata was null, it means there's been a problem in
5804 /* Save copy folder */
5805 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5806 gchar *savefolderid;
5808 savefolderid = compose_get_save_to(compose);
5809 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5810 g_free(savefolderid);
5812 /* Save copy folder */
5813 if (compose->return_receipt) {
5814 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5816 /* Message-ID of message replying to */
5817 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5820 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5821 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5824 /* Message-ID of message forwarding to */
5825 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5828 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5829 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5833 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5834 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5836 /* end of headers */
5837 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5839 if (compose->redirect_filename != NULL) {
5840 if (compose_redirect_write_to_file(compose, fp) < 0) {
5849 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5854 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5858 g_warning("failed to write queue message\n");
5865 if (fclose(fp) == EOF) {
5866 FILE_OP_ERROR(tmp, "fclose");
5873 if (item && *item) {
5876 queue = account_get_special_folder(compose->account, F_QUEUE);
5879 g_warning("can't find queue folder\n");
5885 folder_item_scan(queue);
5886 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5887 g_warning("can't queue the message\n");
5894 if (msgpath == NULL) {
5900 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5901 compose_remove_reedit_target(compose, FALSE);
5904 if ((msgnum != NULL) && (item != NULL)) {
5912 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5915 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5917 struct stat statbuf;
5918 gchar *type, *subtype;
5919 GtkTreeModel *model;
5922 model = gtk_tree_view_get_model(tree_view);
5924 if (!gtk_tree_model_get_iter_first(model, &iter))
5927 gtk_tree_model_get(model, &iter,
5931 if (!is_file_exist(ainfo->file)) {
5932 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5933 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5934 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5936 if (val == G_ALERTDEFAULT) {
5941 mimepart = procmime_mimeinfo_new();
5942 mimepart->content = MIMECONTENT_FILE;
5943 mimepart->data.filename = g_strdup(ainfo->file);
5944 mimepart->tmp = FALSE; /* or we destroy our attachment */
5945 mimepart->offset = 0;
5947 g_stat(ainfo->file, &statbuf);
5948 mimepart->length = statbuf.st_size;
5950 type = g_strdup(ainfo->content_type);
5952 if (!strchr(type, '/')) {
5954 type = g_strdup("application/octet-stream");
5957 subtype = strchr(type, '/') + 1;
5958 *(subtype - 1) = '\0';
5959 mimepart->type = procmime_get_media_type(type);
5960 mimepart->subtype = g_strdup(subtype);
5963 if (mimepart->type == MIMETYPE_MESSAGE &&
5964 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5965 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5968 if (mimepart->type == MIMETYPE_APPLICATION &&
5969 !strcmp2(mimepart->subtype, "octet-stream"))
5970 g_hash_table_insert(mimepart->typeparameters,
5971 g_strdup("name"), g_strdup(ainfo->name));
5972 g_hash_table_insert(mimepart->dispositionparameters,
5973 g_strdup("filename"), g_strdup(ainfo->name));
5974 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5978 if (compose->use_signing) {
5979 if (ainfo->encoding == ENC_7BIT)
5980 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5981 else if (ainfo->encoding == ENC_8BIT)
5982 ainfo->encoding = ENC_BASE64;
5985 procmime_encode_content(mimepart, ainfo->encoding);
5987 g_node_append(parent->node, mimepart->node);
5988 } while (gtk_tree_model_iter_next(model, &iter));
5993 #define IS_IN_CUSTOM_HEADER(header) \
5994 (compose->account->add_customhdr && \
5995 custom_header_find(compose->account->customhdr_list, header) != NULL)
5997 static void compose_add_headerfield_from_headerlist(Compose *compose,
5999 const gchar *fieldname,
6000 const gchar *seperator)
6002 gchar *str, *fieldname_w_colon;
6003 gboolean add_field = FALSE;
6005 ComposeHeaderEntry *headerentry;
6006 const gchar *headerentryname;
6007 const gchar *trans_fieldname;
6010 if (IS_IN_CUSTOM_HEADER(fieldname))
6013 debug_print("Adding %s-fields\n", fieldname);
6015 fieldstr = g_string_sized_new(64);
6017 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6018 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6020 for (list = compose->header_list; list; list = list->next) {
6021 headerentry = ((ComposeHeaderEntry *)list->data);
6022 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6024 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6025 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6027 if (str[0] != '\0') {
6029 g_string_append(fieldstr, seperator);
6030 g_string_append(fieldstr, str);
6039 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6040 compose_convert_header
6041 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6042 strlen(fieldname) + 2, TRUE);
6043 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6047 g_free(fieldname_w_colon);
6048 g_string_free(fieldstr, TRUE);
6053 static gchar *compose_get_header(Compose *compose)
6055 gchar buf[BUFFSIZE];
6056 const gchar *entry_str;
6060 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6062 gchar *from_name = NULL, *from_address = NULL;
6065 cm_return_val_if_fail(compose->account != NULL, NULL);
6066 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6068 header = g_string_sized_new(64);
6071 get_rfc822_date(buf, sizeof(buf));
6072 g_string_append_printf(header, "Date: %s\n", buf);
6076 if (compose->account->name && *compose->account->name) {
6078 QUOTE_IF_REQUIRED(buf, compose->account->name);
6079 tmp = g_strdup_printf("%s <%s>",
6080 buf, compose->account->address);
6082 tmp = g_strdup_printf("%s",
6083 compose->account->address);
6085 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6086 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6088 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6089 from_address = g_strdup(compose->account->address);
6091 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6092 /* extract name and address */
6093 if (strstr(spec, " <") && strstr(spec, ">")) {
6094 from_address = g_strdup(strrchr(spec, '<')+1);
6095 *(strrchr(from_address, '>')) = '\0';
6096 from_name = g_strdup(spec);
6097 *(strrchr(from_name, '<')) = '\0';
6100 from_address = g_strdup(spec);
6107 if (from_name && *from_name) {
6108 compose_convert_header
6109 (compose, buf, sizeof(buf), from_name,
6110 strlen("From: "), TRUE);
6111 QUOTE_IF_REQUIRED(name, buf);
6113 g_string_append_printf(header, "From: %s <%s>\n",
6114 name, from_address);
6116 g_string_append_printf(header, "From: %s\n", from_address);
6119 g_free(from_address);
6122 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6125 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6128 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6132 * If this account is a NNTP account remove Bcc header from
6133 * message body since it otherwise will be publicly shown
6135 if (compose->account->protocol != A_NNTP)
6136 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6139 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6141 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6144 compose_convert_header(compose, buf, sizeof(buf), str,
6145 strlen("Subject: "), FALSE);
6146 g_string_append_printf(header, "Subject: %s\n", buf);
6152 if (compose->account->set_domain && compose->account->domain) {
6153 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6154 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6155 g_snprintf(buf, sizeof(buf), "%s",
6156 strchr(compose->account->address, '@') ?
6157 strchr(compose->account->address, '@')+1 :
6158 compose->account->address);
6160 g_snprintf(buf, sizeof(buf), "%s", "");
6163 if (compose->account->gen_msgid) {
6165 if (compose->account->msgid_with_addr) {
6166 addr = compose->account->address;
6168 generate_msgid(buf, sizeof(buf), addr);
6169 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6170 compose->msgid = g_strdup(buf);
6172 compose->msgid = NULL;
6175 if (compose->remove_references == FALSE) {
6177 if (compose->inreplyto && compose->to_list)
6178 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6181 if (compose->references)
6182 g_string_append_printf(header, "References: %s\n", compose->references);
6186 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6189 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6192 if (compose->account->organization &&
6193 strlen(compose->account->organization) &&
6194 !IS_IN_CUSTOM_HEADER("Organization")) {
6195 compose_convert_header(compose, buf, sizeof(buf),
6196 compose->account->organization,
6197 strlen("Organization: "), FALSE);
6198 g_string_append_printf(header, "Organization: %s\n", buf);
6201 /* Program version and system info */
6202 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6203 !compose->newsgroup_list) {
6204 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6206 gtk_major_version, gtk_minor_version, gtk_micro_version,
6209 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6210 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6212 gtk_major_version, gtk_minor_version, gtk_micro_version,
6216 /* custom headers */
6217 if (compose->account->add_customhdr) {
6220 for (cur = compose->account->customhdr_list; cur != NULL;
6222 CustomHeader *chdr = (CustomHeader *)cur->data;
6224 if (custom_header_is_allowed(chdr->name)) {
6225 compose_convert_header
6226 (compose, buf, sizeof(buf),
6227 chdr->value ? chdr->value : "",
6228 strlen(chdr->name) + 2, FALSE);
6229 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6234 /* Automatic Faces and X-Faces */
6235 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6236 g_string_append_printf(header, "X-Face: %s\n", buf);
6238 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6239 g_string_append_printf(header, "X-Face: %s\n", buf);
6241 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6242 g_string_append_printf(header, "Face: %s\n", buf);
6244 else if (get_default_face (buf, sizeof(buf)) == 0) {
6245 g_string_append_printf(header, "Face: %s\n", buf);
6249 switch (compose->priority) {
6250 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6251 "X-Priority: 1 (Highest)\n");
6253 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6254 "X-Priority: 2 (High)\n");
6256 case PRIORITY_NORMAL: break;
6257 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6258 "X-Priority: 4 (Low)\n");
6260 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6261 "X-Priority: 5 (Lowest)\n");
6263 default: debug_print("compose: priority unknown : %d\n",
6267 /* Request Return Receipt */
6268 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6269 if (compose->return_receipt) {
6270 if (compose->account->name
6271 && *compose->account->name) {
6272 compose_convert_header(compose, buf, sizeof(buf),
6273 compose->account->name,
6274 strlen("Disposition-Notification-To: "),
6276 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6278 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6282 /* get special headers */
6283 for (list = compose->header_list; list; list = list->next) {
6284 ComposeHeaderEntry *headerentry;
6287 gchar *headername_wcolon;
6288 const gchar *headername_trans;
6291 gboolean standard_header = FALSE;
6293 headerentry = ((ComposeHeaderEntry *)list->data);
6295 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6297 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6302 if (!strstr(tmp, ":")) {
6303 headername_wcolon = g_strconcat(tmp, ":", NULL);
6304 headername = g_strdup(tmp);
6306 headername_wcolon = g_strdup(tmp);
6307 headername = g_strdup(strtok(tmp, ":"));
6311 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6312 Xstrdup_a(headervalue, entry_str, return NULL);
6313 subst_char(headervalue, '\r', ' ');
6314 subst_char(headervalue, '\n', ' ');
6315 string = std_headers;
6316 while (*string != NULL) {
6317 headername_trans = prefs_common_translated_header_name(*string);
6318 if (!strcmp(headername_trans, headername_wcolon))
6319 standard_header = TRUE;
6322 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6323 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6326 g_free(headername_wcolon);
6330 g_string_free(header, FALSE);
6335 #undef IS_IN_CUSTOM_HEADER
6337 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6338 gint header_len, gboolean addr_field)
6340 gchar *tmpstr = NULL;
6341 const gchar *out_codeset = NULL;
6343 cm_return_if_fail(src != NULL);
6344 cm_return_if_fail(dest != NULL);
6346 if (len < 1) return;
6348 tmpstr = g_strdup(src);
6350 subst_char(tmpstr, '\n', ' ');
6351 subst_char(tmpstr, '\r', ' ');
6354 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6355 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6356 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6361 codeconv_set_strict(TRUE);
6362 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6363 conv_get_charset_str(compose->out_encoding));
6364 codeconv_set_strict(FALSE);
6366 if (!dest || *dest == '\0') {
6367 gchar *test_conv_global_out = NULL;
6368 gchar *test_conv_reply = NULL;
6370 /* automatic mode. be automatic. */
6371 codeconv_set_strict(TRUE);
6373 out_codeset = conv_get_outgoing_charset_str();
6375 debug_print("trying to convert to %s\n", out_codeset);
6376 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6379 if (!test_conv_global_out && compose->orig_charset
6380 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6381 out_codeset = compose->orig_charset;
6382 debug_print("failure; trying to convert to %s\n", out_codeset);
6383 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6386 if (!test_conv_global_out && !test_conv_reply) {
6388 out_codeset = CS_INTERNAL;
6389 debug_print("finally using %s\n", out_codeset);
6391 g_free(test_conv_global_out);
6392 g_free(test_conv_reply);
6393 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6395 codeconv_set_strict(FALSE);
6400 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6404 cm_return_if_fail(user_data != NULL);
6406 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6407 g_strstrip(address);
6408 if (*address != '\0') {
6409 gchar *name = procheader_get_fromname(address);
6410 extract_address(address);
6411 addressbook_add_contact(name, address, NULL, NULL);
6416 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6418 GtkWidget *menuitem;
6421 cm_return_if_fail(menu != NULL);
6422 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6424 menuitem = gtk_separator_menu_item_new();
6425 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6426 gtk_widget_show(menuitem);
6428 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6429 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6431 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6432 g_strstrip(address);
6433 if (*address == '\0') {
6434 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6437 g_signal_connect(G_OBJECT(menuitem), "activate",
6438 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6439 gtk_widget_show(menuitem);
6442 static void compose_create_header_entry(Compose *compose)
6444 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6451 const gchar *header = NULL;
6452 ComposeHeaderEntry *headerentry;
6453 gboolean standard_header = FALSE;
6454 GtkListStore *model;
6456 #if !(GTK_CHECK_VERSION(2,12,0))
6457 GtkTooltips *tips = compose->tooltips;
6460 headerentry = g_new0(ComposeHeaderEntry, 1);
6463 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6464 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6465 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6467 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6469 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6471 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6472 COMPOSE_NEWSGROUPS);
6473 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6475 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6476 COMPOSE_FOLLOWUPTO);
6478 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6479 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6480 G_CALLBACK(compose_grab_focus_cb), compose);
6481 gtk_widget_show(combo);
6482 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6483 compose->header_nextrow, compose->header_nextrow+1,
6484 GTK_SHRINK, GTK_FILL, 0, 0);
6485 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6486 const gchar *last_header_entry = gtk_entry_get_text(
6487 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6489 while (*string != NULL) {
6490 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6491 standard_header = TRUE;
6494 if (standard_header)
6495 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6497 if (!compose->header_last || !standard_header) {
6498 switch(compose->account->protocol) {
6500 header = prefs_common_translated_header_name("Newsgroups:");
6503 header = prefs_common_translated_header_name("To:");
6508 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6510 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6511 G_CALLBACK(compose_grab_focus_cb), compose);
6513 /* Entry field with cleanup button */
6514 button = gtk_button_new();
6515 gtk_button_set_image(GTK_BUTTON(button),
6516 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6517 gtk_widget_show(button);
6518 CLAWS_SET_TIP(button,
6519 _("Delete entry contents"));
6520 entry = gtk_entry_new();
6521 gtk_widget_show(entry);
6522 CLAWS_SET_TIP(entry,
6523 _("Use <tab> to autocomplete from addressbook"));
6524 hbox = gtk_hbox_new (FALSE, 0);
6525 gtk_widget_show(hbox);
6526 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6527 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6528 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6529 compose->header_nextrow, compose->header_nextrow+1,
6530 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6532 g_signal_connect(G_OBJECT(entry), "key-press-event",
6533 G_CALLBACK(compose_headerentry_key_press_event_cb),
6535 g_signal_connect(G_OBJECT(entry), "changed",
6536 G_CALLBACK(compose_headerentry_changed_cb),
6538 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6539 G_CALLBACK(compose_grab_focus_cb), compose);
6541 g_signal_connect(G_OBJECT(button), "clicked",
6542 G_CALLBACK(compose_headerentry_button_clicked_cb),
6546 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6547 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6548 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6549 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6550 G_CALLBACK(compose_header_drag_received_cb),
6552 g_signal_connect(G_OBJECT(entry), "drag-drop",
6553 G_CALLBACK(compose_drag_drop),
6555 g_signal_connect(G_OBJECT(entry), "populate-popup",
6556 G_CALLBACK(compose_entry_popup_extend),
6559 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6561 headerentry->compose = compose;
6562 headerentry->combo = combo;
6563 headerentry->entry = entry;
6564 headerentry->button = button;
6565 headerentry->hbox = hbox;
6566 headerentry->headernum = compose->header_nextrow;
6567 headerentry->type = PREF_NONE;
6569 compose->header_nextrow++;
6570 compose->header_last = headerentry;
6571 compose->header_list =
6572 g_slist_append(compose->header_list,
6576 static void compose_add_header_entry(Compose *compose, const gchar *header,
6577 gchar *text, ComposePrefType pref_type)
6579 ComposeHeaderEntry *last_header = compose->header_last;
6580 gchar *tmp = g_strdup(text), *email;
6581 gboolean replyto_hdr;
6583 replyto_hdr = (!strcasecmp(header,
6584 prefs_common_translated_header_name("Reply-To:")) ||
6586 prefs_common_translated_header_name("Followup-To:")) ||
6588 prefs_common_translated_header_name("In-Reply-To:")));
6590 extract_address(tmp);
6591 email = g_utf8_strdown(tmp, -1);
6593 if (replyto_hdr == FALSE &&
6594 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6596 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6597 header, text, (gint) pref_type);
6603 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6604 gtk_entry_set_text(GTK_ENTRY(
6605 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6607 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6608 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6609 last_header->type = pref_type;
6611 if (replyto_hdr == FALSE)
6612 g_hash_table_insert(compose->email_hashtable, email,
6613 GUINT_TO_POINTER(1));
6620 static void compose_destroy_headerentry(Compose *compose,
6621 ComposeHeaderEntry *headerentry)
6623 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6626 extract_address(text);
6627 email = g_utf8_strdown(text, -1);
6628 g_hash_table_remove(compose->email_hashtable, email);
6632 gtk_widget_destroy(headerentry->combo);
6633 gtk_widget_destroy(headerentry->entry);
6634 gtk_widget_destroy(headerentry->button);
6635 gtk_widget_destroy(headerentry->hbox);
6636 g_free(headerentry);
6639 static void compose_remove_header_entries(Compose *compose)
6642 for (list = compose->header_list; list; list = list->next)
6643 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6645 compose->header_last = NULL;
6646 g_slist_free(compose->header_list);
6647 compose->header_list = NULL;
6648 compose->header_nextrow = 1;
6649 compose_create_header_entry(compose);
6652 static GtkWidget *compose_create_header(Compose *compose)
6654 GtkWidget *from_optmenu_hbox;
6655 GtkWidget *header_scrolledwin;
6656 GtkWidget *header_table;
6660 /* header labels and entries */
6661 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6662 gtk_widget_show(header_scrolledwin);
6663 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6665 header_table = gtk_table_new(2, 2, FALSE);
6666 gtk_widget_show(header_table);
6667 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6668 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6669 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6672 /* option menu for selecting accounts */
6673 from_optmenu_hbox = compose_account_option_menu_create(compose);
6674 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6675 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6678 compose->header_table = header_table;
6679 compose->header_list = NULL;
6680 compose->header_nextrow = count;
6682 compose_create_header_entry(compose);
6684 compose->table = NULL;
6686 return header_scrolledwin ;
6689 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6691 Compose *compose = (Compose *)data;
6692 GdkEventButton event;
6695 event.time = gtk_get_current_event_time();
6697 return attach_button_pressed(compose->attach_clist, &event, compose);
6700 static GtkWidget *compose_create_attach(Compose *compose)
6702 GtkWidget *attach_scrwin;
6703 GtkWidget *attach_clist;
6705 GtkListStore *store;
6706 GtkCellRenderer *renderer;
6707 GtkTreeViewColumn *column;
6708 GtkTreeSelection *selection;
6710 /* attachment list */
6711 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6712 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6713 GTK_POLICY_AUTOMATIC,
6714 GTK_POLICY_AUTOMATIC);
6715 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6717 store = gtk_list_store_new(N_ATTACH_COLS,
6722 G_TYPE_AUTO_POINTER,
6724 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6725 (GTK_TREE_MODEL(store)));
6726 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6727 g_object_unref(store);
6729 renderer = gtk_cell_renderer_text_new();
6730 column = gtk_tree_view_column_new_with_attributes
6731 (_("Mime type"), renderer, "text",
6732 COL_MIMETYPE, NULL);
6733 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6735 renderer = gtk_cell_renderer_text_new();
6736 column = gtk_tree_view_column_new_with_attributes
6737 (_("Size"), renderer, "text",
6739 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6741 renderer = gtk_cell_renderer_text_new();
6742 column = gtk_tree_view_column_new_with_attributes
6743 (_("Name"), renderer, "text",
6745 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6747 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6748 prefs_common.use_stripes_everywhere);
6749 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6750 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6752 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6753 G_CALLBACK(attach_selected), compose);
6754 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6755 G_CALLBACK(attach_button_pressed), compose);
6757 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6758 G_CALLBACK(popup_attach_button_pressed), compose);
6760 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6761 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6762 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6763 G_CALLBACK(popup_attach_button_pressed), compose);
6765 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6766 G_CALLBACK(attach_key_pressed), compose);
6769 gtk_drag_dest_set(attach_clist,
6770 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6771 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6772 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6773 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6774 G_CALLBACK(compose_attach_drag_received_cb),
6776 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6777 G_CALLBACK(compose_drag_drop),
6780 compose->attach_scrwin = attach_scrwin;
6781 compose->attach_clist = attach_clist;
6783 return attach_scrwin;
6786 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6787 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6789 static GtkWidget *compose_create_others(Compose *compose)
6792 GtkWidget *savemsg_checkbtn;
6793 GtkWidget *savemsg_combo;
6794 GtkWidget *savemsg_select;
6797 gchar *folderidentifier;
6799 /* Table for settings */
6800 table = gtk_table_new(3, 1, FALSE);
6801 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6802 gtk_widget_show(table);
6803 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6806 /* Save Message to folder */
6807 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6808 gtk_widget_show(savemsg_checkbtn);
6809 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6810 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6811 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6813 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6814 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6816 savemsg_combo = gtk_combo_box_entry_new_text();
6817 compose->savemsg_checkbtn = savemsg_checkbtn;
6818 compose->savemsg_combo = savemsg_combo;
6819 gtk_widget_show(savemsg_combo);
6821 if (prefs_common.compose_save_to_history)
6822 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6823 prefs_common.compose_save_to_history);
6825 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6826 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6827 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6828 G_CALLBACK(compose_grab_focus_cb), compose);
6829 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6830 folderidentifier = folder_item_get_identifier(account_get_special_folder
6831 (compose->account, F_OUTBOX));
6832 compose_set_save_to(compose, folderidentifier);
6833 g_free(folderidentifier);
6836 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6837 gtk_widget_show(savemsg_select);
6838 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6839 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6840 G_CALLBACK(compose_savemsg_select_cb),
6848 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6850 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6851 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6854 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6859 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6862 path = folder_item_get_identifier(dest);
6864 compose_set_save_to(compose, path);
6868 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6869 GdkAtom clip, GtkTextIter *insert_place);
6872 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6876 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6878 if (event->button == 3) {
6880 GtkTextIter sel_start, sel_end;
6881 gboolean stuff_selected;
6883 /* move the cursor to allow GtkAspell to check the word
6884 * under the mouse */
6885 if (event->x && event->y) {
6886 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6887 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6889 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6892 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6893 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6896 stuff_selected = gtk_text_buffer_get_selection_bounds(
6898 &sel_start, &sel_end);
6900 gtk_text_buffer_place_cursor (buffer, &iter);
6901 /* reselect stuff */
6903 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6904 gtk_text_buffer_select_range(buffer,
6905 &sel_start, &sel_end);
6907 return FALSE; /* pass the event so that the right-click goes through */
6910 if (event->button == 2) {
6915 /* get the middle-click position to paste at the correct place */
6916 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6917 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6919 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6922 entry_paste_clipboard(compose, text,
6923 prefs_common.linewrap_pastes,
6924 GDK_SELECTION_PRIMARY, &iter);
6932 static void compose_spell_menu_changed(void *data)
6934 Compose *compose = (Compose *)data;
6936 GtkWidget *menuitem;
6937 GtkWidget *parent_item;
6938 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6941 if (compose->gtkaspell == NULL)
6944 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6945 "/Menu/Spelling/Options");
6947 /* setting the submenu removes /Spelling/Options from the factory
6948 * so we need to save it */
6950 if (parent_item == NULL) {
6951 parent_item = compose->aspell_options_menu;
6952 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6954 compose->aspell_options_menu = parent_item;
6956 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6958 spell_menu = g_slist_reverse(spell_menu);
6959 for (items = spell_menu;
6960 items; items = items->next) {
6961 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6962 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6963 gtk_widget_show(GTK_WIDGET(menuitem));
6965 g_slist_free(spell_menu);
6967 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6968 gtk_widget_show(parent_item);
6971 static void compose_dict_changed(void *data)
6973 Compose *compose = (Compose *) data;
6975 if(compose->gtkaspell &&
6976 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6979 gtkaspell_highlight_all(compose->gtkaspell);
6980 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6984 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6986 Compose *compose = (Compose *)data;
6987 GdkEventButton event;
6990 event.time = gtk_get_current_event_time();
6994 return text_clicked(compose->text, &event, compose);
6997 static gboolean compose_force_window_origin = TRUE;
6998 static Compose *compose_create(PrefsAccount *account,
7007 GtkWidget *handlebox;
7009 GtkWidget *notebook;
7011 GtkWidget *attach_hbox;
7012 GtkWidget *attach_lab1;
7013 GtkWidget *attach_lab2;
7018 GtkWidget *subject_hbox;
7019 GtkWidget *subject_frame;
7020 GtkWidget *subject_entry;
7024 GtkWidget *edit_vbox;
7025 GtkWidget *ruler_hbox;
7027 GtkWidget *scrolledwin;
7029 GtkTextBuffer *buffer;
7030 GtkClipboard *clipboard;
7033 UndoMain *undostruct;
7035 gchar *titles[N_ATTACH_COLS];
7036 GtkWidget *popupmenu;
7037 GtkWidget *tmpl_menu;
7038 GtkActionGroup *action_group = NULL;
7041 GtkAspell * gtkaspell = NULL;
7044 static GdkGeometry geometry;
7046 cm_return_val_if_fail(account != NULL, NULL);
7048 debug_print("Creating compose window...\n");
7049 compose = g_new0(Compose, 1);
7051 titles[COL_MIMETYPE] = _("MIME type");
7052 titles[COL_SIZE] = _("Size");
7053 titles[COL_NAME] = _("Name");
7055 compose->batch = batch;
7056 compose->account = account;
7057 compose->folder = folder;
7059 compose->mutex = g_mutex_new();
7060 compose->set_cursor_pos = -1;
7062 #if !(GTK_CHECK_VERSION(2,12,0))
7063 compose->tooltips = tips;
7066 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7068 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7069 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7071 if (!geometry.max_width) {
7072 geometry.max_width = gdk_screen_width();
7073 geometry.max_height = gdk_screen_height();
7076 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7077 &geometry, GDK_HINT_MAX_SIZE);
7078 if (!geometry.min_width) {
7079 geometry.min_width = 600;
7080 geometry.min_height = 440;
7082 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7083 &geometry, GDK_HINT_MIN_SIZE);
7085 #ifndef GENERIC_UMPC
7086 if (compose_force_window_origin)
7087 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7088 prefs_common.compose_y);
7090 g_signal_connect(G_OBJECT(window), "delete_event",
7091 G_CALLBACK(compose_delete_cb), compose);
7092 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7093 gtk_widget_realize(window);
7095 gtkut_widget_set_composer_icon(window);
7097 vbox = gtk_vbox_new(FALSE, 0);
7098 gtk_container_add(GTK_CONTAINER(window), vbox);
7100 compose->ui_manager = gtk_ui_manager_new();
7101 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7102 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7103 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7104 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7105 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7106 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7107 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7108 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7109 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7110 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7133 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7159 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7212 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7217 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7219 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7227 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7229 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7230 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)
7231 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)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7237 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)
7238 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)
7240 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7243 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)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7246 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7247 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)
7248 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7253 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)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7255 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7258 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7259 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)
7260 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)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7264 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7267 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7268 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7269 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7271 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7272 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7273 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)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7276 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7277 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7281 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7282 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7283 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7284 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7285 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7286 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7289 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7291 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7292 gtk_widget_show_all(menubar);
7294 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7296 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7298 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7301 if (prefs_common.toolbar_detachable) {
7302 handlebox = gtk_handle_box_new();
7304 handlebox = gtk_hbox_new(FALSE, 0);
7306 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7308 gtk_widget_realize(handlebox);
7310 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7313 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7317 vbox2 = gtk_vbox_new(FALSE, 2);
7318 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7319 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7322 notebook = gtk_notebook_new();
7323 gtk_widget_set_size_request(notebook, -1, 130);
7324 gtk_widget_show(notebook);
7326 /* header labels and entries */
7327 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7328 compose_create_header(compose),
7329 gtk_label_new_with_mnemonic(_("Hea_der")));
7330 /* attachment list */
7331 attach_hbox = gtk_hbox_new(FALSE, 0);
7332 gtk_widget_show(attach_hbox);
7334 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7335 gtk_widget_show(attach_lab1);
7336 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7338 attach_lab2 = gtk_label_new("");
7339 gtk_widget_show(attach_lab2);
7340 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7342 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7343 compose_create_attach(compose),
7346 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7347 compose_create_others(compose),
7348 gtk_label_new_with_mnemonic(_("Othe_rs")));
7351 subject_hbox = gtk_hbox_new(FALSE, 0);
7352 gtk_widget_show(subject_hbox);
7354 subject_frame = gtk_frame_new(NULL);
7355 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7356 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7357 gtk_widget_show(subject_frame);
7359 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7360 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7361 gtk_widget_show(subject);
7363 label = gtk_label_new(_("Subject:"));
7364 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7365 gtk_widget_show(label);
7368 subject_entry = claws_spell_entry_new();
7370 subject_entry = gtk_entry_new();
7372 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7373 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7374 G_CALLBACK(compose_grab_focus_cb), compose);
7375 gtk_widget_show(subject_entry);
7376 compose->subject_entry = subject_entry;
7377 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7379 edit_vbox = gtk_vbox_new(FALSE, 0);
7381 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7384 ruler_hbox = gtk_hbox_new(FALSE, 0);
7385 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7387 ruler = gtk_shruler_new();
7388 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7389 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7393 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7394 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7395 GTK_POLICY_AUTOMATIC,
7396 GTK_POLICY_AUTOMATIC);
7397 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7399 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7400 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7402 text = gtk_text_view_new();
7403 if (prefs_common.show_compose_margin) {
7404 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7405 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7407 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7408 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7409 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7410 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7411 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7413 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7415 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7416 G_CALLBACK(compose_edit_size_alloc),
7418 g_signal_connect(G_OBJECT(buffer), "changed",
7419 G_CALLBACK(compose_changed_cb), compose);
7420 g_signal_connect(G_OBJECT(text), "grab_focus",
7421 G_CALLBACK(compose_grab_focus_cb), compose);
7422 g_signal_connect(G_OBJECT(buffer), "insert_text",
7423 G_CALLBACK(text_inserted), compose);
7424 g_signal_connect(G_OBJECT(text), "button_press_event",
7425 G_CALLBACK(text_clicked), compose);
7427 g_signal_connect(G_OBJECT(text), "popup-menu",
7428 G_CALLBACK(compose_popup_menu), compose);
7430 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7431 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7432 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7433 G_CALLBACK(compose_popup_menu), compose);
7435 g_signal_connect(G_OBJECT(subject_entry), "changed",
7436 G_CALLBACK(compose_changed_cb), compose);
7439 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7440 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7441 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7442 g_signal_connect(G_OBJECT(text), "drag_data_received",
7443 G_CALLBACK(compose_insert_drag_received_cb),
7445 g_signal_connect(G_OBJECT(text), "drag-drop",
7446 G_CALLBACK(compose_drag_drop),
7448 gtk_widget_show_all(vbox);
7450 /* pane between attach clist and text */
7451 paned = gtk_vpaned_new();
7452 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7454 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7455 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7457 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7459 gtk_paned_add1(GTK_PANED(paned), notebook);
7460 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7461 gtk_widget_show_all(paned);
7464 if (prefs_common.textfont) {
7465 PangoFontDescription *font_desc;
7467 font_desc = pango_font_description_from_string
7468 (prefs_common.textfont);
7470 gtk_widget_modify_font(text, font_desc);
7471 pango_font_description_free(font_desc);
7475 gtk_action_group_add_actions(action_group, compose_popup_entries,
7476 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7482 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7484 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7486 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7487 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7488 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7490 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7492 undostruct = undo_init(text);
7493 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7496 address_completion_start(window);
7498 compose->window = window;
7499 compose->vbox = vbox;
7500 compose->menubar = menubar;
7501 compose->handlebox = handlebox;
7503 compose->vbox2 = vbox2;
7505 compose->paned = paned;
7507 compose->attach_label = attach_lab2;
7509 compose->notebook = notebook;
7510 compose->edit_vbox = edit_vbox;
7511 compose->ruler_hbox = ruler_hbox;
7512 compose->ruler = ruler;
7513 compose->scrolledwin = scrolledwin;
7514 compose->text = text;
7516 compose->focused_editable = NULL;
7518 compose->popupmenu = popupmenu;
7520 compose->tmpl_menu = tmpl_menu;
7522 compose->mode = mode;
7523 compose->rmode = mode;
7525 compose->targetinfo = NULL;
7526 compose->replyinfo = NULL;
7527 compose->fwdinfo = NULL;
7529 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7530 g_str_equal, (GDestroyNotify) g_free, NULL);
7532 compose->replyto = NULL;
7534 compose->bcc = NULL;
7535 compose->followup_to = NULL;
7537 compose->ml_post = NULL;
7539 compose->inreplyto = NULL;
7540 compose->references = NULL;
7541 compose->msgid = NULL;
7542 compose->boundary = NULL;
7544 compose->autowrap = prefs_common.autowrap;
7545 compose->autoindent = prefs_common.auto_indent;
7546 compose->use_signing = FALSE;
7547 compose->use_encryption = FALSE;
7548 compose->privacy_system = NULL;
7550 compose->modified = FALSE;
7552 compose->return_receipt = FALSE;
7554 compose->to_list = NULL;
7555 compose->newsgroup_list = NULL;
7557 compose->undostruct = undostruct;
7559 compose->sig_str = NULL;
7561 compose->exteditor_file = NULL;
7562 compose->exteditor_pid = -1;
7563 compose->exteditor_tag = -1;
7564 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7567 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7568 if (mode != COMPOSE_REDIRECT) {
7569 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7570 strcmp(prefs_common.dictionary, "")) {
7571 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7572 prefs_common.alt_dictionary,
7573 conv_get_locale_charset_str(),
7574 prefs_common.misspelled_col,
7575 prefs_common.check_while_typing,
7576 prefs_common.recheck_when_changing_dict,
7577 prefs_common.use_alternate,
7578 prefs_common.use_both_dicts,
7579 GTK_TEXT_VIEW(text),
7580 GTK_WINDOW(compose->window),
7581 compose_dict_changed,
7582 compose_spell_menu_changed,
7585 alertpanel_error(_("Spell checker could not "
7587 gtkaspell_checkers_strerror());
7588 gtkaspell_checkers_reset_error();
7590 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7594 compose->gtkaspell = gtkaspell;
7595 compose_spell_menu_changed(compose);
7596 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7599 compose_select_account(compose, account, TRUE);
7601 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7602 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7604 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7605 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7607 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7608 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7610 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7611 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7613 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7614 if (account->protocol != A_NNTP)
7615 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7616 prefs_common_translated_header_name("To:"));
7618 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7619 prefs_common_translated_header_name("Newsgroups:"));
7621 addressbook_set_target_compose(compose);
7623 if (mode != COMPOSE_REDIRECT)
7624 compose_set_template_menu(compose);
7626 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7629 compose_list = g_list_append(compose_list, compose);
7631 if (!prefs_common.show_ruler)
7632 gtk_widget_hide(ruler_hbox);
7634 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7637 compose->priority = PRIORITY_NORMAL;
7638 compose_update_priority_menu_item(compose);
7640 compose_set_out_encoding(compose);
7643 compose_update_actions_menu(compose);
7645 /* Privacy Systems menu */
7646 compose_update_privacy_systems_menu(compose);
7648 activate_privacy_system(compose, account, TRUE);
7649 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7651 gtk_widget_realize(window);
7653 gtk_widget_show(window);
7655 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7656 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7663 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7668 GtkWidget *optmenubox;
7671 GtkWidget *from_name = NULL;
7672 #if !(GTK_CHECK_VERSION(2,12,0))
7673 GtkTooltips *tips = compose->tooltips;
7676 gint num = 0, def_menu = 0;
7678 accounts = account_get_list();
7679 cm_return_val_if_fail(accounts != NULL, NULL);
7681 optmenubox = gtk_event_box_new();
7682 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7683 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7685 hbox = gtk_hbox_new(FALSE, 6);
7686 from_name = gtk_entry_new();
7688 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7689 G_CALLBACK(compose_grab_focus_cb), compose);
7691 for (; accounts != NULL; accounts = accounts->next, num++) {
7692 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7693 gchar *name, *from = NULL;
7695 if (ac == compose->account) def_menu = num;
7697 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7700 if (ac == compose->account) {
7701 if (ac->name && *ac->name) {
7703 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7704 from = g_strdup_printf("%s <%s>",
7706 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7708 from = g_strdup_printf("%s",
7710 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7713 COMBOBOX_ADD(menu, name, ac->account_id);
7718 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7720 g_signal_connect(G_OBJECT(optmenu), "changed",
7721 G_CALLBACK(account_activated),
7723 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7724 G_CALLBACK(compose_entry_popup_extend),
7727 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7728 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7730 CLAWS_SET_TIP(optmenubox,
7731 _("Account to use for this email"));
7732 CLAWS_SET_TIP(from_name,
7733 _("Sender address to be used"));
7735 compose->account_combo = optmenu;
7736 compose->from_name = from_name;
7741 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7743 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7744 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7745 Compose *compose = (Compose *) data;
7747 compose->priority = value;
7751 static void compose_reply_change_mode(Compose *compose,
7754 gboolean was_modified = compose->modified;
7756 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7758 cm_return_if_fail(compose->replyinfo != NULL);
7760 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7762 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7764 if (action == COMPOSE_REPLY_TO_ALL)
7766 if (action == COMPOSE_REPLY_TO_SENDER)
7768 if (action == COMPOSE_REPLY_TO_LIST)
7771 compose_remove_header_entries(compose);
7772 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7773 if (compose->account->set_autocc && compose->account->auto_cc)
7774 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7776 if (compose->account->set_autobcc && compose->account->auto_bcc)
7777 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7779 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7780 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7781 compose_show_first_last_header(compose, TRUE);
7782 compose->modified = was_modified;
7783 compose_set_title(compose);
7786 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7788 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7789 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7790 Compose *compose = (Compose *) data;
7793 compose_reply_change_mode(compose, value);
7796 static void compose_update_priority_menu_item(Compose * compose)
7798 GtkWidget *menuitem = NULL;
7799 switch (compose->priority) {
7800 case PRIORITY_HIGHEST:
7801 menuitem = gtk_ui_manager_get_widget
7802 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7805 menuitem = gtk_ui_manager_get_widget
7806 (compose->ui_manager, "/Menu/Options/Priority/High");
7808 case PRIORITY_NORMAL:
7809 menuitem = gtk_ui_manager_get_widget
7810 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7813 menuitem = gtk_ui_manager_get_widget
7814 (compose->ui_manager, "/Menu/Options/Priority/Low");
7816 case PRIORITY_LOWEST:
7817 menuitem = gtk_ui_manager_get_widget
7818 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7821 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7824 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7826 Compose *compose = (Compose *) data;
7828 gboolean can_sign = FALSE, can_encrypt = FALSE;
7830 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7832 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7835 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7836 g_free(compose->privacy_system);
7837 compose->privacy_system = NULL;
7838 if (systemid != NULL) {
7839 compose->privacy_system = g_strdup(systemid);
7841 can_sign = privacy_system_can_sign(systemid);
7842 can_encrypt = privacy_system_can_encrypt(systemid);
7845 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7847 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7848 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7851 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7853 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7854 GtkWidget *menuitem = NULL;
7856 gboolean can_sign = FALSE, can_encrypt = FALSE;
7857 gboolean found = FALSE;
7859 if (compose->privacy_system != NULL) {
7861 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7862 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7863 cm_return_if_fail(menuitem != NULL);
7865 amenu = GTK_MENU_SHELL(menuitem)->children;
7867 while (amenu != NULL) {
7868 GList *alist = amenu->next;
7870 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7871 if (systemid != NULL) {
7872 if (strcmp(systemid, compose->privacy_system) == 0 &&
7873 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7874 menuitem = GTK_WIDGET(amenu->data);
7876 can_sign = privacy_system_can_sign(systemid);
7877 can_encrypt = privacy_system_can_encrypt(systemid);
7881 } else if (strlen(compose->privacy_system) == 0 &&
7882 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7883 menuitem = GTK_WIDGET(amenu->data);
7886 can_encrypt = FALSE;
7893 if (menuitem != NULL)
7894 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7896 if (warn && !found && strlen(compose->privacy_system)) {
7897 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7898 "will not be able to sign or encrypt this message."),
7899 compose->privacy_system);
7903 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7904 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7907 static void compose_set_out_encoding(Compose *compose)
7909 CharSet out_encoding;
7910 const gchar *branch = NULL;
7911 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7913 switch(out_encoding) {
7914 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7915 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7916 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7917 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7918 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7919 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7920 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7921 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7922 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7923 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7924 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7925 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7926 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7927 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7928 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7929 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7930 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7931 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7932 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7933 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7934 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7935 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7936 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7937 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
7938 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7939 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7940 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7941 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7942 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7943 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7944 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7945 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7946 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7948 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7951 static void compose_set_template_menu(Compose *compose)
7953 GSList *tmpl_list, *cur;
7957 tmpl_list = template_get_config();
7959 menu = gtk_menu_new();
7961 gtk_menu_set_accel_group (GTK_MENU (menu),
7962 gtk_ui_manager_get_accel_group(compose->ui_manager));
7963 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7964 Template *tmpl = (Template *)cur->data;
7965 gchar *accel_path = NULL;
7966 item = gtk_menu_item_new_with_label(tmpl->name);
7967 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7968 g_signal_connect(G_OBJECT(item), "activate",
7969 G_CALLBACK(compose_template_activate_cb),
7971 g_object_set_data(G_OBJECT(item), "template", tmpl);
7972 gtk_widget_show(item);
7973 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7974 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7978 gtk_widget_show(menu);
7979 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7982 void compose_update_actions_menu(Compose *compose)
7984 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7987 static void compose_update_privacy_systems_menu(Compose *compose)
7989 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7990 GSList *systems, *cur;
7992 GtkWidget *system_none;
7994 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7995 GtkWidget *privacy_menu = gtk_menu_new();
7997 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7998 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8000 g_signal_connect(G_OBJECT(system_none), "activate",
8001 G_CALLBACK(compose_set_privacy_system_cb), compose);
8003 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8004 gtk_widget_show(system_none);
8006 systems = privacy_get_system_ids();
8007 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8008 gchar *systemid = cur->data;
8010 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8011 widget = gtk_radio_menu_item_new_with_label(group,
8012 privacy_system_get_name(systemid));
8013 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8014 g_strdup(systemid), g_free);
8015 g_signal_connect(G_OBJECT(widget), "activate",
8016 G_CALLBACK(compose_set_privacy_system_cb), compose);
8018 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8019 gtk_widget_show(widget);
8022 g_slist_free(systems);
8023 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8024 gtk_widget_show_all(privacy_menu);
8025 gtk_widget_show_all(privacy_menuitem);
8028 void compose_reflect_prefs_all(void)
8033 for (cur = compose_list; cur != NULL; cur = cur->next) {
8034 compose = (Compose *)cur->data;
8035 compose_set_template_menu(compose);
8039 void compose_reflect_prefs_pixmap_theme(void)
8044 for (cur = compose_list; cur != NULL; cur = cur->next) {
8045 compose = (Compose *)cur->data;
8046 toolbar_update(TOOLBAR_COMPOSE, compose);
8050 static const gchar *compose_quote_char_from_context(Compose *compose)
8052 const gchar *qmark = NULL;
8054 cm_return_val_if_fail(compose != NULL, NULL);
8056 switch (compose->mode) {
8057 /* use forward-specific quote char */
8058 case COMPOSE_FORWARD:
8059 case COMPOSE_FORWARD_AS_ATTACH:
8060 case COMPOSE_FORWARD_INLINE:
8061 if (compose->folder && compose->folder->prefs &&
8062 compose->folder->prefs->forward_with_format)
8063 qmark = compose->folder->prefs->forward_quotemark;
8064 else if (compose->account->forward_with_format)
8065 qmark = compose->account->forward_quotemark;
8067 qmark = prefs_common.fw_quotemark;
8070 /* use reply-specific quote char in all other modes */
8072 if (compose->folder && compose->folder->prefs &&
8073 compose->folder->prefs->reply_with_format)
8074 qmark = compose->folder->prefs->reply_quotemark;
8075 else if (compose->account->reply_with_format)
8076 qmark = compose->account->reply_quotemark;
8078 qmark = prefs_common.quotemark;
8082 if (qmark == NULL || *qmark == '\0')
8088 static void compose_template_apply(Compose *compose, Template *tmpl,
8092 GtkTextBuffer *buffer;
8096 gchar *parsed_str = NULL;
8097 gint cursor_pos = 0;
8098 const gchar *err_msg = _("The body of the template has an error at line %d.");
8101 /* process the body */
8103 text = GTK_TEXT_VIEW(compose->text);
8104 buffer = gtk_text_view_get_buffer(text);
8107 qmark = compose_quote_char_from_context(compose);
8109 if (compose->replyinfo != NULL) {
8112 gtk_text_buffer_set_text(buffer, "", -1);
8113 mark = gtk_text_buffer_get_insert(buffer);
8114 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8116 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8117 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8119 } else if (compose->fwdinfo != NULL) {
8122 gtk_text_buffer_set_text(buffer, "", -1);
8123 mark = gtk_text_buffer_get_insert(buffer);
8124 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8126 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8127 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8130 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8132 GtkTextIter start, end;
8135 gtk_text_buffer_get_start_iter(buffer, &start);
8136 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8137 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8139 /* clear the buffer now */
8141 gtk_text_buffer_set_text(buffer, "", -1);
8143 parsed_str = compose_quote_fmt(compose, dummyinfo,
8144 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8145 procmsg_msginfo_free( dummyinfo );
8151 gtk_text_buffer_set_text(buffer, "", -1);
8152 mark = gtk_text_buffer_get_insert(buffer);
8153 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8156 if (replace && parsed_str && compose->account->auto_sig)
8157 compose_insert_sig(compose, FALSE);
8159 if (replace && parsed_str) {
8160 gtk_text_buffer_get_start_iter(buffer, &iter);
8161 gtk_text_buffer_place_cursor(buffer, &iter);
8165 cursor_pos = quote_fmt_get_cursor_pos();
8166 compose->set_cursor_pos = cursor_pos;
8167 if (cursor_pos == -1)
8169 gtk_text_buffer_get_start_iter(buffer, &iter);
8170 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8171 gtk_text_buffer_place_cursor(buffer, &iter);
8174 /* process the other fields */
8176 compose_template_apply_fields(compose, tmpl);
8177 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8178 quote_fmt_reset_vartable();
8179 compose_changed_cb(NULL, compose);
8182 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8183 gtkaspell_highlight_all(compose->gtkaspell);
8187 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8189 MsgInfo* dummyinfo = NULL;
8190 MsgInfo *msginfo = NULL;
8193 if (compose->replyinfo != NULL)
8194 msginfo = compose->replyinfo;
8195 else if (compose->fwdinfo != NULL)
8196 msginfo = compose->fwdinfo;
8198 dummyinfo = compose_msginfo_new_from_compose(compose);
8199 msginfo = dummyinfo;
8202 if (tmpl->from && *tmpl->from != '\0') {
8204 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8205 compose->gtkaspell);
8207 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8209 quote_fmt_scan_string(tmpl->from);
8212 buf = quote_fmt_get_buffer();
8214 alertpanel_error(_("Template From format error."));
8216 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8220 if (tmpl->to && *tmpl->to != '\0') {
8222 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8223 compose->gtkaspell);
8225 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8227 quote_fmt_scan_string(tmpl->to);
8230 buf = quote_fmt_get_buffer();
8232 alertpanel_error(_("Template To format error."));
8234 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8238 if (tmpl->cc && *tmpl->cc != '\0') {
8240 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8241 compose->gtkaspell);
8243 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8245 quote_fmt_scan_string(tmpl->cc);
8248 buf = quote_fmt_get_buffer();
8250 alertpanel_error(_("Template Cc format error."));
8252 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8256 if (tmpl->bcc && *tmpl->bcc != '\0') {
8258 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8259 compose->gtkaspell);
8261 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8263 quote_fmt_scan_string(tmpl->bcc);
8266 buf = quote_fmt_get_buffer();
8268 alertpanel_error(_("Template Bcc format error."));
8270 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8274 /* process the subject */
8275 if (tmpl->subject && *tmpl->subject != '\0') {
8277 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8278 compose->gtkaspell);
8280 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8282 quote_fmt_scan_string(tmpl->subject);
8285 buf = quote_fmt_get_buffer();
8287 alertpanel_error(_("Template subject format error."));
8289 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8293 procmsg_msginfo_free( dummyinfo );
8296 static void compose_destroy(Compose *compose)
8298 GtkTextBuffer *buffer;
8299 GtkClipboard *clipboard;
8301 compose_list = g_list_remove(compose_list, compose);
8303 if (compose->updating) {
8304 debug_print("danger, not destroying anything now\n");
8305 compose->deferred_destroy = TRUE;
8308 /* NOTE: address_completion_end() does nothing with the window
8309 * however this may change. */
8310 address_completion_end(compose->window);
8312 slist_free_strings(compose->to_list);
8313 g_slist_free(compose->to_list);
8314 slist_free_strings(compose->newsgroup_list);
8315 g_slist_free(compose->newsgroup_list);
8316 slist_free_strings(compose->header_list);
8317 g_slist_free(compose->header_list);
8319 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8321 g_hash_table_destroy(compose->email_hashtable);
8323 procmsg_msginfo_free(compose->targetinfo);
8324 procmsg_msginfo_free(compose->replyinfo);
8325 procmsg_msginfo_free(compose->fwdinfo);
8327 g_free(compose->replyto);
8328 g_free(compose->cc);
8329 g_free(compose->bcc);
8330 g_free(compose->newsgroups);
8331 g_free(compose->followup_to);
8333 g_free(compose->ml_post);
8335 g_free(compose->inreplyto);
8336 g_free(compose->references);
8337 g_free(compose->msgid);
8338 g_free(compose->boundary);
8340 g_free(compose->redirect_filename);
8341 if (compose->undostruct)
8342 undo_destroy(compose->undostruct);
8344 g_free(compose->sig_str);
8346 g_free(compose->exteditor_file);
8348 g_free(compose->orig_charset);
8350 g_free(compose->privacy_system);
8352 if (addressbook_get_target_compose() == compose)
8353 addressbook_set_target_compose(NULL);
8356 if (compose->gtkaspell) {
8357 gtkaspell_delete(compose->gtkaspell);
8358 compose->gtkaspell = NULL;
8362 if (!compose->batch) {
8363 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8364 prefs_common.compose_height = compose->window->allocation.height;
8367 if (!gtk_widget_get_parent(compose->paned))
8368 gtk_widget_destroy(compose->paned);
8369 gtk_widget_destroy(compose->popupmenu);
8371 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8372 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8373 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8375 gtk_widget_destroy(compose->window);
8376 toolbar_destroy(compose->toolbar);
8377 g_free(compose->toolbar);
8378 g_mutex_free(compose->mutex);
8382 static void compose_attach_info_free(AttachInfo *ainfo)
8384 g_free(ainfo->file);
8385 g_free(ainfo->content_type);
8386 g_free(ainfo->name);
8390 static void compose_attach_update_label(Compose *compose)
8395 GtkTreeModel *model;
8400 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8401 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8402 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8406 while(gtk_tree_model_iter_next(model, &iter))
8409 text = g_strdup_printf("(%d)", i);
8410 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8414 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8416 Compose *compose = (Compose *)data;
8417 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8418 GtkTreeSelection *selection;
8420 GtkTreeModel *model;
8422 selection = gtk_tree_view_get_selection(tree_view);
8423 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8428 for (cur = sel; cur != NULL; cur = cur->next) {
8429 GtkTreePath *path = cur->data;
8430 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8433 gtk_tree_path_free(path);
8436 for (cur = sel; cur != NULL; cur = cur->next) {
8437 GtkTreeRowReference *ref = cur->data;
8438 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8441 if (gtk_tree_model_get_iter(model, &iter, path))
8442 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8444 gtk_tree_path_free(path);
8445 gtk_tree_row_reference_free(ref);
8449 compose_attach_update_label(compose);
8452 static struct _AttachProperty
8455 GtkWidget *mimetype_entry;
8456 GtkWidget *encoding_optmenu;
8457 GtkWidget *path_entry;
8458 GtkWidget *filename_entry;
8460 GtkWidget *cancel_btn;
8463 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8465 gtk_tree_path_free((GtkTreePath *)ptr);
8468 static void compose_attach_property(GtkAction *action, gpointer data)
8470 Compose *compose = (Compose *)data;
8471 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8473 GtkComboBox *optmenu;
8474 GtkTreeSelection *selection;
8476 GtkTreeModel *model;
8479 static gboolean cancelled;
8481 /* only if one selected */
8482 selection = gtk_tree_view_get_selection(tree_view);
8483 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8486 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8490 path = (GtkTreePath *) sel->data;
8491 gtk_tree_model_get_iter(model, &iter, path);
8492 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8495 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8501 if (!attach_prop.window)
8502 compose_attach_property_create(&cancelled);
8503 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8504 gtk_widget_grab_focus(attach_prop.ok_btn);
8505 gtk_widget_show(attach_prop.window);
8506 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8508 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8509 if (ainfo->encoding == ENC_UNKNOWN)
8510 combobox_select_by_data(optmenu, ENC_BASE64);
8512 combobox_select_by_data(optmenu, ainfo->encoding);
8514 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8515 ainfo->content_type ? ainfo->content_type : "");
8516 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8517 ainfo->file ? ainfo->file : "");
8518 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8519 ainfo->name ? ainfo->name : "");
8522 const gchar *entry_text;
8524 gchar *cnttype = NULL;
8531 gtk_widget_hide(attach_prop.window);
8532 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8537 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8538 if (*entry_text != '\0') {
8541 text = g_strstrip(g_strdup(entry_text));
8542 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8543 cnttype = g_strdup(text);
8546 alertpanel_error(_("Invalid MIME type."));
8552 ainfo->encoding = combobox_get_active_data(optmenu);
8554 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8555 if (*entry_text != '\0') {
8556 if (is_file_exist(entry_text) &&
8557 (size = get_file_size(entry_text)) > 0)
8558 file = g_strdup(entry_text);
8561 (_("File doesn't exist or is empty."));
8567 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8568 if (*entry_text != '\0') {
8569 g_free(ainfo->name);
8570 ainfo->name = g_strdup(entry_text);
8574 g_free(ainfo->content_type);
8575 ainfo->content_type = cnttype;
8578 g_free(ainfo->file);
8582 ainfo->size = (goffset)size;
8584 /* update tree store */
8585 text = to_human_readable(ainfo->size);
8586 gtk_tree_model_get_iter(model, &iter, path);
8587 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8588 COL_MIMETYPE, ainfo->content_type,
8590 COL_NAME, ainfo->name,
8596 gtk_tree_path_free(path);
8599 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8601 label = gtk_label_new(str); \
8602 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8603 GTK_FILL, 0, 0, 0); \
8604 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8606 entry = gtk_entry_new(); \
8607 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8608 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8611 static void compose_attach_property_create(gboolean *cancelled)
8617 GtkWidget *mimetype_entry;
8620 GtkListStore *optmenu_menu;
8621 GtkWidget *path_entry;
8622 GtkWidget *filename_entry;
8625 GtkWidget *cancel_btn;
8626 GList *mime_type_list, *strlist;
8629 debug_print("Creating attach_property window...\n");
8631 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8632 gtk_widget_set_size_request(window, 480, -1);
8633 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8634 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8635 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8636 g_signal_connect(G_OBJECT(window), "delete_event",
8637 G_CALLBACK(attach_property_delete_event),
8639 g_signal_connect(G_OBJECT(window), "key_press_event",
8640 G_CALLBACK(attach_property_key_pressed),
8643 vbox = gtk_vbox_new(FALSE, 8);
8644 gtk_container_add(GTK_CONTAINER(window), vbox);
8646 table = gtk_table_new(4, 2, FALSE);
8647 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8648 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8649 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8651 label = gtk_label_new(_("MIME type"));
8652 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8654 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8655 mimetype_entry = gtk_combo_box_entry_new_text();
8656 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8657 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8659 /* stuff with list */
8660 mime_type_list = procmime_get_mime_type_list();
8662 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8663 MimeType *type = (MimeType *) mime_type_list->data;
8666 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8668 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8671 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8672 (GCompareFunc)strcmp2);
8675 for (mime_type_list = strlist; mime_type_list != NULL;
8676 mime_type_list = mime_type_list->next) {
8677 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8678 g_free(mime_type_list->data);
8680 g_list_free(strlist);
8681 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8682 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8684 label = gtk_label_new(_("Encoding"));
8685 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8687 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8689 hbox = gtk_hbox_new(FALSE, 0);
8690 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8691 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8693 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8694 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8696 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8697 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8698 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8699 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8700 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8702 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8704 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8705 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8707 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8708 &ok_btn, GTK_STOCK_OK,
8710 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8711 gtk_widget_grab_default(ok_btn);
8713 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8714 G_CALLBACK(attach_property_ok),
8716 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8717 G_CALLBACK(attach_property_cancel),
8720 gtk_widget_show_all(vbox);
8722 attach_prop.window = window;
8723 attach_prop.mimetype_entry = mimetype_entry;
8724 attach_prop.encoding_optmenu = optmenu;
8725 attach_prop.path_entry = path_entry;
8726 attach_prop.filename_entry = filename_entry;
8727 attach_prop.ok_btn = ok_btn;
8728 attach_prop.cancel_btn = cancel_btn;
8731 #undef SET_LABEL_AND_ENTRY
8733 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8739 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8745 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8746 gboolean *cancelled)
8754 static gboolean attach_property_key_pressed(GtkWidget *widget,
8756 gboolean *cancelled)
8758 if (event && event->keyval == GDK_Escape) {
8762 if (event && event->keyval == GDK_Return) {
8770 static void compose_exec_ext_editor(Compose *compose)
8777 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8778 G_DIR_SEPARATOR, compose);
8780 if (pipe(pipe_fds) < 0) {
8786 if ((pid = fork()) < 0) {
8793 /* close the write side of the pipe */
8796 compose->exteditor_file = g_strdup(tmp);
8797 compose->exteditor_pid = pid;
8799 compose_set_ext_editor_sensitive(compose, FALSE);
8802 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8804 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8806 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8810 } else { /* process-monitoring process */
8816 /* close the read side of the pipe */
8819 if (compose_write_body_to_file(compose, tmp) < 0) {
8820 fd_write_all(pipe_fds[1], "2\n", 2);
8824 pid_ed = compose_exec_ext_editor_real(tmp);
8826 fd_write_all(pipe_fds[1], "1\n", 2);
8830 /* wait until editor is terminated */
8831 waitpid(pid_ed, NULL, 0);
8833 fd_write_all(pipe_fds[1], "0\n", 2);
8840 #endif /* G_OS_UNIX */
8844 static gint compose_exec_ext_editor_real(const gchar *file)
8851 cm_return_val_if_fail(file != NULL, -1);
8853 if ((pid = fork()) < 0) {
8858 if (pid != 0) return pid;
8860 /* grandchild process */
8862 if (setpgid(0, getppid()))
8865 if (prefs_common_get_ext_editor_cmd() &&
8866 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8867 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8868 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8870 if (prefs_common_get_ext_editor_cmd())
8871 g_warning("External editor command-line is invalid: '%s'\n",
8872 prefs_common_get_ext_editor_cmd());
8873 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8876 cmdline = strsplit_with_quote(buf, " ", 1024);
8877 execvp(cmdline[0], cmdline);
8880 g_strfreev(cmdline);
8885 static gboolean compose_ext_editor_kill(Compose *compose)
8887 pid_t pgid = compose->exteditor_pid * -1;
8890 ret = kill(pgid, 0);
8892 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8896 msg = g_strdup_printf
8897 (_("The external editor is still working.\n"
8898 "Force terminating the process?\n"
8899 "process group id: %d"), -pgid);
8900 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8901 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8905 if (val == G_ALERTALTERNATE) {
8906 g_source_remove(compose->exteditor_tag);
8907 g_io_channel_shutdown(compose->exteditor_ch,
8909 g_io_channel_unref(compose->exteditor_ch);
8911 if (kill(pgid, SIGTERM) < 0) perror("kill");
8912 waitpid(compose->exteditor_pid, NULL, 0);
8914 g_warning("Terminated process group id: %d", -pgid);
8915 g_warning("Temporary file: %s",
8916 compose->exteditor_file);
8918 compose_set_ext_editor_sensitive(compose, TRUE);
8920 g_free(compose->exteditor_file);
8921 compose->exteditor_file = NULL;
8922 compose->exteditor_pid = -1;
8923 compose->exteditor_ch = NULL;
8924 compose->exteditor_tag = -1;
8932 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8936 Compose *compose = (Compose *)data;
8939 debug_print(_("Compose: input from monitoring process\n"));
8941 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8943 g_io_channel_shutdown(source, FALSE, NULL);
8944 g_io_channel_unref(source);
8946 waitpid(compose->exteditor_pid, NULL, 0);
8948 if (buf[0] == '0') { /* success */
8949 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8950 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8952 gtk_text_buffer_set_text(buffer, "", -1);
8953 compose_insert_file(compose, compose->exteditor_file);
8954 compose_changed_cb(NULL, compose);
8955 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8957 if (claws_unlink(compose->exteditor_file) < 0)
8958 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8959 } else if (buf[0] == '1') { /* failed */
8960 g_warning("Couldn't exec external editor\n");
8961 if (claws_unlink(compose->exteditor_file) < 0)
8962 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8963 } else if (buf[0] == '2') {
8964 g_warning("Couldn't write to file\n");
8965 } else if (buf[0] == '3') {
8966 g_warning("Pipe read failed\n");
8969 compose_set_ext_editor_sensitive(compose, TRUE);
8971 g_free(compose->exteditor_file);
8972 compose->exteditor_file = NULL;
8973 compose->exteditor_pid = -1;
8974 compose->exteditor_ch = NULL;
8975 compose->exteditor_tag = -1;
8980 static void compose_set_ext_editor_sensitive(Compose *compose,
8983 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8988 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8989 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8991 gtk_widget_set_sensitive(compose->text, sensitive);
8992 if (compose->toolbar->send_btn)
8993 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8994 if (compose->toolbar->sendl_btn)
8995 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8996 if (compose->toolbar->draft_btn)
8997 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8998 if (compose->toolbar->insert_btn)
8999 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9000 if (compose->toolbar->sig_btn)
9001 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9002 if (compose->toolbar->exteditor_btn)
9003 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9004 if (compose->toolbar->linewrap_current_btn)
9005 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9006 if (compose->toolbar->linewrap_all_btn)
9007 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9009 #endif /* G_OS_UNIX */
9012 * compose_undo_state_changed:
9014 * Change the sensivity of the menuentries undo and redo
9016 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9017 gint redo_state, gpointer data)
9019 Compose *compose = (Compose *)data;
9021 switch (undo_state) {
9022 case UNDO_STATE_TRUE:
9023 if (!undostruct->undo_state) {
9024 undostruct->undo_state = TRUE;
9025 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9028 case UNDO_STATE_FALSE:
9029 if (undostruct->undo_state) {
9030 undostruct->undo_state = FALSE;
9031 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9034 case UNDO_STATE_UNCHANGED:
9036 case UNDO_STATE_REFRESH:
9037 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9040 g_warning("Undo state not recognized");
9044 switch (redo_state) {
9045 case UNDO_STATE_TRUE:
9046 if (!undostruct->redo_state) {
9047 undostruct->redo_state = TRUE;
9048 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9051 case UNDO_STATE_FALSE:
9052 if (undostruct->redo_state) {
9053 undostruct->redo_state = FALSE;
9054 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9057 case UNDO_STATE_UNCHANGED:
9059 case UNDO_STATE_REFRESH:
9060 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9063 g_warning("Redo state not recognized");
9068 /* callback functions */
9070 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9071 * includes "non-client" (windows-izm) in calculation, so this calculation
9072 * may not be accurate.
9074 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9075 GtkAllocation *allocation,
9076 GtkSHRuler *shruler)
9078 if (prefs_common.show_ruler) {
9079 gint char_width = 0, char_height = 0;
9080 gint line_width_in_chars;
9082 gtkut_get_font_size(GTK_WIDGET(widget),
9083 &char_width, &char_height);
9084 line_width_in_chars =
9085 (allocation->width - allocation->x) / char_width;
9087 /* got the maximum */
9088 gtk_ruler_set_range(GTK_RULER(shruler),
9089 0.0, line_width_in_chars, 0,
9090 /*line_width_in_chars*/ char_width);
9099 ComposePrefType type;
9100 gboolean entry_marked;
9103 static void account_activated(GtkComboBox *optmenu, gpointer data)
9105 Compose *compose = (Compose *)data;
9108 gchar *folderidentifier;
9109 gint account_id = 0;
9112 GSList *list, *saved_list = NULL;
9113 HeaderEntryState *state;
9114 GtkRcStyle *style = NULL;
9115 static GdkColor yellow;
9116 static gboolean color_set = FALSE;
9118 /* Get ID of active account in the combo box */
9119 menu = gtk_combo_box_get_model(optmenu);
9120 gtk_combo_box_get_active_iter(optmenu, &iter);
9121 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9123 ac = account_find_from_id(account_id);
9124 cm_return_if_fail(ac != NULL);
9126 if (ac != compose->account) {
9127 compose_select_account(compose, ac, FALSE);
9129 for (list = compose->header_list; list; list = list->next) {
9130 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9132 if (hentry->type == PREF_ACCOUNT || !list->next) {
9133 compose_destroy_headerentry(compose, hentry);
9137 state = g_malloc0(sizeof(HeaderEntryState));
9138 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9139 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9140 state->entry = gtk_editable_get_chars(
9141 GTK_EDITABLE(hentry->entry), 0, -1);
9142 state->type = hentry->type;
9145 gdk_color_parse("#f5f6be", &yellow);
9146 color_set = gdk_colormap_alloc_color(
9147 gdk_colormap_get_system(),
9148 &yellow, FALSE, TRUE);
9151 style = gtk_widget_get_modifier_style(hentry->entry);
9152 state->entry_marked = gdk_color_equal(&yellow,
9153 &style->base[GTK_STATE_NORMAL]);
9155 saved_list = g_slist_append(saved_list, state);
9156 compose_destroy_headerentry(compose, hentry);
9159 compose->header_last = NULL;
9160 g_slist_free(compose->header_list);
9161 compose->header_list = NULL;
9162 compose->header_nextrow = 1;
9163 compose_create_header_entry(compose);
9165 if (ac->set_autocc && ac->auto_cc)
9166 compose_entry_append(compose, ac->auto_cc,
9167 COMPOSE_CC, PREF_ACCOUNT);
9169 if (ac->set_autobcc && ac->auto_bcc)
9170 compose_entry_append(compose, ac->auto_bcc,
9171 COMPOSE_BCC, PREF_ACCOUNT);
9173 if (ac->set_autoreplyto && ac->auto_replyto)
9174 compose_entry_append(compose, ac->auto_replyto,
9175 COMPOSE_REPLYTO, PREF_ACCOUNT);
9177 for (list = saved_list; list; list = list->next) {
9178 state = (HeaderEntryState *) list->data;
9180 compose_add_header_entry(compose, state->header,
9181 state->entry, state->type);
9182 if (state->entry_marked)
9183 compose_entry_mark_default_to(compose, state->entry);
9185 g_free(state->header);
9186 g_free(state->entry);
9189 g_slist_free(saved_list);
9191 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9192 (ac->protocol == A_NNTP) ?
9193 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9196 /* Set message save folder */
9197 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9198 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9200 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9201 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9203 compose_set_save_to(compose, NULL);
9204 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9205 folderidentifier = folder_item_get_identifier(account_get_special_folder
9206 (compose->account, F_OUTBOX));
9207 compose_set_save_to(compose, folderidentifier);
9208 g_free(folderidentifier);
9212 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9213 GtkTreeViewColumn *column, Compose *compose)
9215 compose_attach_property(NULL, compose);
9218 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9221 Compose *compose = (Compose *)data;
9222 GtkTreeSelection *attach_selection;
9223 gint attach_nr_selected;
9225 if (!event) return FALSE;
9227 if (event->button == 3) {
9228 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9229 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9231 if (attach_nr_selected > 0)
9233 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9234 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9236 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9237 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9240 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9241 NULL, NULL, event->button, event->time);
9248 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9251 Compose *compose = (Compose *)data;
9253 if (!event) return FALSE;
9255 switch (event->keyval) {
9257 compose_attach_remove_selected(NULL, compose);
9263 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9265 toolbar_comp_set_sensitive(compose, allow);
9266 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9267 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9269 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9271 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9272 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9273 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9275 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9279 static void compose_send_cb(GtkAction *action, gpointer data)
9281 Compose *compose = (Compose *)data;
9283 if (prefs_common.work_offline &&
9284 !inc_offline_should_override(TRUE,
9285 _("Claws Mail needs network access in order "
9286 "to send this email.")))
9289 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9290 g_source_remove(compose->draft_timeout_tag);
9291 compose->draft_timeout_tag = -1;
9294 compose_send(compose);
9297 static void compose_send_later_cb(GtkAction *action, gpointer data)
9299 Compose *compose = (Compose *)data;
9303 compose_allow_user_actions(compose, FALSE);
9304 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9305 compose_allow_user_actions(compose, TRUE);
9309 compose_close(compose);
9310 } else if (val == -1) {
9311 alertpanel_error(_("Could not queue message."));
9312 } else if (val == -2) {
9313 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9314 } else if (val == -3) {
9315 if (privacy_peek_error())
9316 alertpanel_error(_("Could not queue message for sending:\n\n"
9317 "Signature failed: %s"), privacy_get_error());
9318 } else if (val == -4) {
9319 alertpanel_error(_("Could not queue message for sending:\n\n"
9320 "Charset conversion failed."));
9321 } else if (val == -5) {
9322 alertpanel_error(_("Could not queue message for sending:\n\n"
9323 "Couldn't get recipient encryption key."));
9324 } else if (val == -6) {
9327 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9330 #define DRAFTED_AT_EXIT "drafted_at_exit"
9331 static void compose_register_draft(MsgInfo *info)
9333 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9334 DRAFTED_AT_EXIT, NULL);
9335 FILE *fp = g_fopen(filepath, "ab");
9338 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9346 gboolean compose_draft (gpointer data, guint action)
9348 Compose *compose = (Compose *)data;
9352 MsgFlags flag = {0, 0};
9353 static gboolean lock = FALSE;
9354 MsgInfo *newmsginfo;
9356 gboolean target_locked = FALSE;
9357 gboolean err = FALSE;
9359 if (lock) return FALSE;
9361 if (compose->sending)
9364 draft = account_get_special_folder(compose->account, F_DRAFT);
9365 cm_return_val_if_fail(draft != NULL, FALSE);
9367 if (!g_mutex_trylock(compose->mutex)) {
9368 /* we don't want to lock the mutex once it's available,
9369 * because as the only other part of compose.c locking
9370 * it is compose_close - which means once unlocked,
9371 * the compose struct will be freed */
9372 debug_print("couldn't lock mutex, probably sending\n");
9378 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9379 G_DIR_SEPARATOR, compose);
9380 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9381 FILE_OP_ERROR(tmp, "fopen");
9385 /* chmod for security */
9386 if (change_file_mode_rw(fp, tmp) < 0) {
9387 FILE_OP_ERROR(tmp, "chmod");
9388 g_warning("can't change file mode\n");
9391 /* Save draft infos */
9392 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9393 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9395 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9396 gchar *savefolderid;
9398 savefolderid = compose_get_save_to(compose);
9399 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9400 g_free(savefolderid);
9402 if (compose->return_receipt) {
9403 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9405 if (compose->privacy_system) {
9406 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9407 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9408 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9411 /* Message-ID of message replying to */
9412 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9415 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9416 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9419 /* Message-ID of message forwarding to */
9420 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9423 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9424 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9428 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9429 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9431 /* end of headers */
9432 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9439 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9443 if (fclose(fp) == EOF) {
9447 if (compose->targetinfo) {
9448 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9449 flag.perm_flags = target_locked?MSG_LOCKED:0;
9451 flag.tmp_flags = MSG_DRAFT;
9453 folder_item_scan(draft);
9454 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9455 MsgInfo *tmpinfo = NULL;
9456 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9457 if (compose->msgid) {
9458 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9461 msgnum = tmpinfo->msgnum;
9462 procmsg_msginfo_free(tmpinfo);
9463 debug_print("got draft msgnum %d from scanning\n", msgnum);
9465 debug_print("didn't get draft msgnum after scanning\n");
9468 debug_print("got draft msgnum %d from adding\n", msgnum);
9474 if (action != COMPOSE_AUTO_SAVE) {
9475 if (action != COMPOSE_DRAFT_FOR_EXIT)
9476 alertpanel_error(_("Could not save draft."));
9479 gtkut_window_popup(compose->window);
9480 val = alertpanel_full(_("Could not save draft"),
9481 _("Could not save draft.\n"
9482 "Do you want to cancel exit or discard this email?"),
9483 _("_Cancel exit"), _("_Discard email"), NULL,
9484 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9485 if (val == G_ALERTALTERNATE) {
9487 g_mutex_unlock(compose->mutex); /* must be done before closing */
9488 compose_close(compose);
9492 g_mutex_unlock(compose->mutex); /* must be done before closing */
9501 if (compose->mode == COMPOSE_REEDIT) {
9502 compose_remove_reedit_target(compose, TRUE);
9505 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9508 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9510 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9512 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9513 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9514 procmsg_msginfo_set_flags(newmsginfo, 0,
9515 MSG_HAS_ATTACHMENT);
9517 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9518 compose_register_draft(newmsginfo);
9520 procmsg_msginfo_free(newmsginfo);
9523 folder_item_scan(draft);
9525 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9527 g_mutex_unlock(compose->mutex); /* must be done before closing */
9528 compose_close(compose);
9534 path = folder_item_fetch_msg(draft, msgnum);
9536 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9539 if (g_stat(path, &s) < 0) {
9540 FILE_OP_ERROR(path, "stat");
9546 procmsg_msginfo_free(compose->targetinfo);
9547 compose->targetinfo = procmsg_msginfo_new();
9548 compose->targetinfo->msgnum = msgnum;
9549 compose->targetinfo->size = (goffset)s.st_size;
9550 compose->targetinfo->mtime = s.st_mtime;
9551 compose->targetinfo->folder = draft;
9553 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9554 compose->mode = COMPOSE_REEDIT;
9556 if (action == COMPOSE_AUTO_SAVE) {
9557 compose->autosaved_draft = compose->targetinfo;
9559 compose->modified = FALSE;
9560 compose_set_title(compose);
9564 g_mutex_unlock(compose->mutex);
9568 void compose_clear_exit_drafts(void)
9570 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9571 DRAFTED_AT_EXIT, NULL);
9572 if (is_file_exist(filepath))
9573 claws_unlink(filepath);
9578 void compose_reopen_exit_drafts(void)
9580 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9581 DRAFTED_AT_EXIT, NULL);
9582 FILE *fp = g_fopen(filepath, "rb");
9586 while (fgets(buf, sizeof(buf), fp)) {
9587 gchar **parts = g_strsplit(buf, "\t", 2);
9588 const gchar *folder = parts[0];
9589 int msgnum = parts[1] ? atoi(parts[1]):-1;
9591 if (folder && *folder && msgnum > -1) {
9592 FolderItem *item = folder_find_item_from_identifier(folder);
9593 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9595 compose_reedit(info, FALSE);
9602 compose_clear_exit_drafts();
9605 static void compose_save_cb(GtkAction *action, gpointer data)
9607 Compose *compose = (Compose *)data;
9608 compose_draft(compose, COMPOSE_KEEP_EDITING);
9609 compose->rmode = COMPOSE_REEDIT;
9612 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9614 if (compose && file_list) {
9617 for ( tmp = file_list; tmp; tmp = tmp->next) {
9618 gchar *file = (gchar *) tmp->data;
9619 gchar *utf8_filename = conv_filename_to_utf8(file);
9620 compose_attach_append(compose, file, utf8_filename, NULL);
9621 compose_changed_cb(NULL, compose);
9626 g_free(utf8_filename);
9631 static void compose_attach_cb(GtkAction *action, gpointer data)
9633 Compose *compose = (Compose *)data;
9636 if (compose->redirect_filename != NULL)
9639 file_list = filesel_select_multiple_files_open(_("Select file"));
9642 compose_attach_from_list(compose, file_list, TRUE);
9643 g_list_free(file_list);
9647 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9649 Compose *compose = (Compose *)data;
9651 gint files_inserted = 0;
9653 file_list = filesel_select_multiple_files_open(_("Select file"));
9658 for ( tmp = file_list; tmp; tmp = tmp->next) {
9659 gchar *file = (gchar *) tmp->data;
9660 gchar *filedup = g_strdup(file);
9661 gchar *shortfile = g_path_get_basename(filedup);
9662 ComposeInsertResult res;
9663 /* insert the file if the file is short or if the user confirmed that
9664 he/she wants to insert the large file */
9665 res = compose_insert_file(compose, file);
9666 if (res == COMPOSE_INSERT_READ_ERROR) {
9667 alertpanel_error(_("File '%s' could not be read."), shortfile);
9668 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9669 alertpanel_error(_("File '%s' contained invalid characters\n"
9670 "for the current encoding, insertion may be incorrect."),
9672 } else if (res == COMPOSE_INSERT_SUCCESS)
9679 g_list_free(file_list);
9683 if (files_inserted > 0 && compose->gtkaspell &&
9684 compose->gtkaspell->check_while_typing)
9685 gtkaspell_highlight_all(compose->gtkaspell);
9689 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9691 Compose *compose = (Compose *)data;
9693 compose_insert_sig(compose, FALSE);
9696 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9700 Compose *compose = (Compose *)data;
9702 gtkut_widget_get_uposition(widget, &x, &y);
9703 if (!compose->batch) {
9704 prefs_common.compose_x = x;
9705 prefs_common.compose_y = y;
9707 if (compose->sending || compose->updating)
9709 compose_close_cb(NULL, compose);
9713 void compose_close_toolbar(Compose *compose)
9715 compose_close_cb(NULL, compose);
9718 static void compose_close_cb(GtkAction *action, gpointer data)
9720 Compose *compose = (Compose *)data;
9724 if (compose->exteditor_tag != -1) {
9725 if (!compose_ext_editor_kill(compose))
9730 if (compose->modified) {
9731 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9732 if (!g_mutex_trylock(compose->mutex)) {
9733 /* we don't want to lock the mutex once it's available,
9734 * because as the only other part of compose.c locking
9735 * it is compose_close - which means once unlocked,
9736 * the compose struct will be freed */
9737 debug_print("couldn't lock mutex, probably sending\n");
9741 val = alertpanel(_("Discard message"),
9742 _("This message has been modified. Discard it?"),
9743 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9745 val = alertpanel(_("Save changes"),
9746 _("This message has been modified. Save the latest changes?"),
9747 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9749 g_mutex_unlock(compose->mutex);
9751 case G_ALERTDEFAULT:
9752 if (prefs_common.autosave && !reedit)
9753 compose_remove_draft(compose);
9755 case G_ALERTALTERNATE:
9756 compose_draft(data, COMPOSE_QUIT_EDITING);
9763 compose_close(compose);
9766 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9768 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9769 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9770 Compose *compose = (Compose *) data;
9773 compose->out_encoding = (CharSet)value;
9776 static void compose_address_cb(GtkAction *action, gpointer data)
9778 Compose *compose = (Compose *)data;
9780 addressbook_open(compose);
9783 static void about_show_cb(GtkAction *action, gpointer data)
9788 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9790 Compose *compose = (Compose *)data;
9795 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9796 cm_return_if_fail(tmpl != NULL);
9798 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9800 val = alertpanel(_("Apply template"), msg,
9801 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9804 if (val == G_ALERTDEFAULT)
9805 compose_template_apply(compose, tmpl, TRUE);
9806 else if (val == G_ALERTALTERNATE)
9807 compose_template_apply(compose, tmpl, FALSE);
9810 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9812 Compose *compose = (Compose *)data;
9814 compose_exec_ext_editor(compose);
9817 static void compose_undo_cb(GtkAction *action, gpointer data)
9819 Compose *compose = (Compose *)data;
9820 gboolean prev_autowrap = compose->autowrap;
9822 compose->autowrap = FALSE;
9823 undo_undo(compose->undostruct);
9824 compose->autowrap = prev_autowrap;
9827 static void compose_redo_cb(GtkAction *action, gpointer data)
9829 Compose *compose = (Compose *)data;
9830 gboolean prev_autowrap = compose->autowrap;
9832 compose->autowrap = FALSE;
9833 undo_redo(compose->undostruct);
9834 compose->autowrap = prev_autowrap;
9837 static void entry_cut_clipboard(GtkWidget *entry)
9839 if (GTK_IS_EDITABLE(entry))
9840 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9841 else if (GTK_IS_TEXT_VIEW(entry))
9842 gtk_text_buffer_cut_clipboard(
9843 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9844 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9848 static void entry_copy_clipboard(GtkWidget *entry)
9850 if (GTK_IS_EDITABLE(entry))
9851 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9852 else if (GTK_IS_TEXT_VIEW(entry))
9853 gtk_text_buffer_copy_clipboard(
9854 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9855 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9858 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9859 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9861 if (GTK_IS_TEXT_VIEW(entry)) {
9862 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9863 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9864 GtkTextIter start_iter, end_iter;
9866 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9868 if (contents == NULL)
9871 /* we shouldn't delete the selection when middle-click-pasting, or we
9872 * can't mid-click-paste our own selection */
9873 if (clip != GDK_SELECTION_PRIMARY) {
9874 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9875 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9878 if (insert_place == NULL) {
9879 /* if insert_place isn't specified, insert at the cursor.
9880 * used for Ctrl-V pasting */
9881 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9882 start = gtk_text_iter_get_offset(&start_iter);
9883 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9885 /* if insert_place is specified, paste here.
9886 * used for mid-click-pasting */
9887 start = gtk_text_iter_get_offset(insert_place);
9888 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9889 if (prefs_common.primary_paste_unselects)
9890 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9894 /* paste unwrapped: mark the paste so it's not wrapped later */
9895 end = start + strlen(contents);
9896 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9897 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9898 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9899 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9900 /* rewrap paragraph now (after a mid-click-paste) */
9901 mark_start = gtk_text_buffer_get_insert(buffer);
9902 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9903 gtk_text_iter_backward_char(&start_iter);
9904 compose_beautify_paragraph(compose, &start_iter, TRUE);
9906 } else if (GTK_IS_EDITABLE(entry))
9907 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9909 compose->modified = TRUE;
9912 static void entry_allsel(GtkWidget *entry)
9914 if (GTK_IS_EDITABLE(entry))
9915 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9916 else if (GTK_IS_TEXT_VIEW(entry)) {
9917 GtkTextIter startiter, enditer;
9918 GtkTextBuffer *textbuf;
9920 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9921 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9922 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9924 gtk_text_buffer_move_mark_by_name(textbuf,
9925 "selection_bound", &startiter);
9926 gtk_text_buffer_move_mark_by_name(textbuf,
9927 "insert", &enditer);
9931 static void compose_cut_cb(GtkAction *action, gpointer data)
9933 Compose *compose = (Compose *)data;
9934 if (compose->focused_editable
9935 #ifndef GENERIC_UMPC
9936 && gtkut_widget_has_focus(compose->focused_editable)
9939 entry_cut_clipboard(compose->focused_editable);
9942 static void compose_copy_cb(GtkAction *action, gpointer data)
9944 Compose *compose = (Compose *)data;
9945 if (compose->focused_editable
9946 #ifndef GENERIC_UMPC
9947 && gtkut_widget_has_focus(compose->focused_editable)
9950 entry_copy_clipboard(compose->focused_editable);
9953 static void compose_paste_cb(GtkAction *action, gpointer data)
9955 Compose *compose = (Compose *)data;
9957 GtkTextBuffer *buffer;
9959 if (compose->focused_editable &&
9960 gtkut_widget_has_focus(compose->focused_editable))
9961 entry_paste_clipboard(compose, compose->focused_editable,
9962 prefs_common.linewrap_pastes,
9963 GDK_SELECTION_CLIPBOARD, NULL);
9967 if (gtkut_widget_has_focus(compose->text) &&
9968 compose->gtkaspell &&
9969 compose->gtkaspell->check_while_typing)
9970 gtkaspell_highlight_all(compose->gtkaspell);
9974 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9976 Compose *compose = (Compose *)data;
9977 gint wrap_quote = prefs_common.linewrap_quote;
9978 if (compose->focused_editable
9979 #ifndef GENERIC_UMPC
9980 && gtkut_widget_has_focus(compose->focused_editable)
9983 /* let text_insert() (called directly or at a later time
9984 * after the gtk_editable_paste_clipboard) know that
9985 * text is to be inserted as a quotation. implemented
9986 * by using a simple refcount... */
9987 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9988 G_OBJECT(compose->focused_editable),
9989 "paste_as_quotation"));
9990 g_object_set_data(G_OBJECT(compose->focused_editable),
9991 "paste_as_quotation",
9992 GINT_TO_POINTER(paste_as_quotation + 1));
9993 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9994 entry_paste_clipboard(compose, compose->focused_editable,
9995 prefs_common.linewrap_pastes,
9996 GDK_SELECTION_CLIPBOARD, NULL);
9997 prefs_common.linewrap_quote = wrap_quote;
10001 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10003 Compose *compose = (Compose *)data;
10004 gint prev_autowrap;
10005 GtkTextBuffer *buffer;
10007 if (compose->focused_editable
10008 #ifndef GENERIC_UMPC
10009 && gtkut_widget_has_focus(compose->focused_editable)
10012 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10013 GDK_SELECTION_CLIPBOARD, NULL);
10017 if (gtkut_widget_has_focus(compose->text) &&
10018 compose->gtkaspell &&
10019 compose->gtkaspell->check_while_typing)
10020 gtkaspell_highlight_all(compose->gtkaspell);
10024 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10026 Compose *compose = (Compose *)data;
10027 gint prev_autowrap;
10028 GtkTextBuffer *buffer;
10030 if (compose->focused_editable
10031 #ifndef GENERIC_UMPC
10032 && gtkut_widget_has_focus(compose->focused_editable)
10035 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10036 GDK_SELECTION_CLIPBOARD, NULL);
10040 if (gtkut_widget_has_focus(compose->text) &&
10041 compose->gtkaspell &&
10042 compose->gtkaspell->check_while_typing)
10043 gtkaspell_highlight_all(compose->gtkaspell);
10047 static void compose_allsel_cb(GtkAction *action, gpointer data)
10049 Compose *compose = (Compose *)data;
10050 if (compose->focused_editable
10051 #ifndef GENERIC_UMPC
10052 && gtkut_widget_has_focus(compose->focused_editable)
10055 entry_allsel(compose->focused_editable);
10058 static void textview_move_beginning_of_line (GtkTextView *text)
10060 GtkTextBuffer *buffer;
10064 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10066 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10067 mark = gtk_text_buffer_get_insert(buffer);
10068 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10069 gtk_text_iter_set_line_offset(&ins, 0);
10070 gtk_text_buffer_place_cursor(buffer, &ins);
10073 static void textview_move_forward_character (GtkTextView *text)
10075 GtkTextBuffer *buffer;
10079 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10081 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10082 mark = gtk_text_buffer_get_insert(buffer);
10083 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10084 if (gtk_text_iter_forward_cursor_position(&ins))
10085 gtk_text_buffer_place_cursor(buffer, &ins);
10088 static void textview_move_backward_character (GtkTextView *text)
10090 GtkTextBuffer *buffer;
10094 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10096 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10097 mark = gtk_text_buffer_get_insert(buffer);
10098 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10099 if (gtk_text_iter_backward_cursor_position(&ins))
10100 gtk_text_buffer_place_cursor(buffer, &ins);
10103 static void textview_move_forward_word (GtkTextView *text)
10105 GtkTextBuffer *buffer;
10110 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10112 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10113 mark = gtk_text_buffer_get_insert(buffer);
10114 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10115 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10116 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10117 gtk_text_iter_backward_word_start(&ins);
10118 gtk_text_buffer_place_cursor(buffer, &ins);
10122 static void textview_move_backward_word (GtkTextView *text)
10124 GtkTextBuffer *buffer;
10129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10132 mark = gtk_text_buffer_get_insert(buffer);
10133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10134 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10135 if (gtk_text_iter_backward_word_starts(&ins, 1))
10136 gtk_text_buffer_place_cursor(buffer, &ins);
10139 static void textview_move_end_of_line (GtkTextView *text)
10141 GtkTextBuffer *buffer;
10145 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10147 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10148 mark = gtk_text_buffer_get_insert(buffer);
10149 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10150 if (gtk_text_iter_forward_to_line_end(&ins))
10151 gtk_text_buffer_place_cursor(buffer, &ins);
10154 static void textview_move_next_line (GtkTextView *text)
10156 GtkTextBuffer *buffer;
10161 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10163 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10164 mark = gtk_text_buffer_get_insert(buffer);
10165 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10166 offset = gtk_text_iter_get_line_offset(&ins);
10167 if (gtk_text_iter_forward_line(&ins)) {
10168 gtk_text_iter_set_line_offset(&ins, offset);
10169 gtk_text_buffer_place_cursor(buffer, &ins);
10173 static void textview_move_previous_line (GtkTextView *text)
10175 GtkTextBuffer *buffer;
10180 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10182 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10183 mark = gtk_text_buffer_get_insert(buffer);
10184 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10185 offset = gtk_text_iter_get_line_offset(&ins);
10186 if (gtk_text_iter_backward_line(&ins)) {
10187 gtk_text_iter_set_line_offset(&ins, offset);
10188 gtk_text_buffer_place_cursor(buffer, &ins);
10192 static void textview_delete_forward_character (GtkTextView *text)
10194 GtkTextBuffer *buffer;
10196 GtkTextIter ins, end_iter;
10198 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10200 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10201 mark = gtk_text_buffer_get_insert(buffer);
10202 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10204 if (gtk_text_iter_forward_char(&end_iter)) {
10205 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10209 static void textview_delete_backward_character (GtkTextView *text)
10211 GtkTextBuffer *buffer;
10213 GtkTextIter ins, end_iter;
10215 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10217 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10218 mark = gtk_text_buffer_get_insert(buffer);
10219 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10221 if (gtk_text_iter_backward_char(&end_iter)) {
10222 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10226 static void textview_delete_forward_word (GtkTextView *text)
10228 GtkTextBuffer *buffer;
10230 GtkTextIter ins, end_iter;
10232 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10234 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10235 mark = gtk_text_buffer_get_insert(buffer);
10236 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10238 if (gtk_text_iter_forward_word_end(&end_iter)) {
10239 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10243 static void textview_delete_backward_word (GtkTextView *text)
10245 GtkTextBuffer *buffer;
10247 GtkTextIter ins, end_iter;
10249 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10251 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10252 mark = gtk_text_buffer_get_insert(buffer);
10253 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10255 if (gtk_text_iter_backward_word_start(&end_iter)) {
10256 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10260 static void textview_delete_line (GtkTextView *text)
10262 GtkTextBuffer *buffer;
10264 GtkTextIter ins, start_iter, end_iter;
10266 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10268 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10269 mark = gtk_text_buffer_get_insert(buffer);
10270 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10273 gtk_text_iter_set_line_offset(&start_iter, 0);
10276 if (gtk_text_iter_ends_line(&end_iter)){
10277 if (!gtk_text_iter_forward_char(&end_iter))
10278 gtk_text_iter_backward_char(&start_iter);
10281 gtk_text_iter_forward_to_line_end(&end_iter);
10282 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10285 static void textview_delete_to_line_end (GtkTextView *text)
10287 GtkTextBuffer *buffer;
10289 GtkTextIter ins, end_iter;
10291 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10293 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10294 mark = gtk_text_buffer_get_insert(buffer);
10295 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10297 if (gtk_text_iter_ends_line(&end_iter))
10298 gtk_text_iter_forward_char(&end_iter);
10300 gtk_text_iter_forward_to_line_end(&end_iter);
10301 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10304 #define DO_ACTION(name, act) { \
10305 if(!strcmp(name, a_name)) { \
10309 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10311 const gchar *a_name = gtk_action_get_name(action);
10312 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10313 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10314 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10315 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10316 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10317 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10318 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10319 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10320 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10321 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10322 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10323 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10324 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10325 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10329 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10331 Compose *compose = (Compose *)data;
10332 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10333 ComposeCallAdvancedAction action = -1;
10335 action = compose_call_advanced_action_from_path(gaction);
10338 void (*do_action) (GtkTextView *text);
10339 } action_table[] = {
10340 {textview_move_beginning_of_line},
10341 {textview_move_forward_character},
10342 {textview_move_backward_character},
10343 {textview_move_forward_word},
10344 {textview_move_backward_word},
10345 {textview_move_end_of_line},
10346 {textview_move_next_line},
10347 {textview_move_previous_line},
10348 {textview_delete_forward_character},
10349 {textview_delete_backward_character},
10350 {textview_delete_forward_word},
10351 {textview_delete_backward_word},
10352 {textview_delete_line},
10353 {textview_delete_to_line_end}
10356 if (!gtkut_widget_has_focus(GTK_WIDGET(text))) return;
10358 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10359 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10360 if (action_table[action].do_action)
10361 action_table[action].do_action(text);
10363 g_warning("Not implemented yet.");
10367 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10371 if (GTK_IS_EDITABLE(widget)) {
10372 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10373 gtk_editable_set_position(GTK_EDITABLE(widget),
10376 if (widget->parent && widget->parent->parent
10377 && widget->parent->parent->parent) {
10378 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10379 gint y = widget->allocation.y;
10380 gint height = widget->allocation.height;
10381 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10382 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10384 if (y < (int)shown->value) {
10385 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10387 if (y + height > (int)shown->value + (int)shown->page_size) {
10388 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10389 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10390 y + height - (int)shown->page_size - 1);
10392 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10393 (int)shown->upper - (int)shown->page_size - 1);
10400 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10401 compose->focused_editable = widget;
10403 #ifdef GENERIC_UMPC
10404 if (GTK_IS_TEXT_VIEW(widget)
10405 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10406 g_object_ref(compose->notebook);
10407 g_object_ref(compose->edit_vbox);
10408 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10409 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10410 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10411 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10412 g_object_unref(compose->notebook);
10413 g_object_unref(compose->edit_vbox);
10414 g_signal_handlers_block_by_func(G_OBJECT(widget),
10415 G_CALLBACK(compose_grab_focus_cb),
10417 gtk_widget_grab_focus(widget);
10418 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10419 G_CALLBACK(compose_grab_focus_cb),
10421 } else if (!GTK_IS_TEXT_VIEW(widget)
10422 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10423 g_object_ref(compose->notebook);
10424 g_object_ref(compose->edit_vbox);
10425 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10426 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10427 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10428 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10429 g_object_unref(compose->notebook);
10430 g_object_unref(compose->edit_vbox);
10431 g_signal_handlers_block_by_func(G_OBJECT(widget),
10432 G_CALLBACK(compose_grab_focus_cb),
10434 gtk_widget_grab_focus(widget);
10435 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10436 G_CALLBACK(compose_grab_focus_cb),
10442 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10444 compose->modified = TRUE;
10445 // compose_beautify_paragraph(compose, NULL, TRUE);
10446 #ifndef GENERIC_UMPC
10447 compose_set_title(compose);
10451 static void compose_wrap_cb(GtkAction *action, gpointer data)
10453 Compose *compose = (Compose *)data;
10454 compose_beautify_paragraph(compose, NULL, TRUE);
10457 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10459 Compose *compose = (Compose *)data;
10460 compose_wrap_all_full(compose, TRUE);
10463 static void compose_find_cb(GtkAction *action, gpointer data)
10465 Compose *compose = (Compose *)data;
10467 message_search_compose(compose);
10470 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10473 Compose *compose = (Compose *)data;
10474 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10475 if (compose->autowrap)
10476 compose_wrap_all_full(compose, TRUE);
10477 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10480 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10483 Compose *compose = (Compose *)data;
10484 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10487 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10489 Compose *compose = (Compose *)data;
10491 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10494 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10496 Compose *compose = (Compose *)data;
10498 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10501 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10503 g_free(compose->privacy_system);
10505 compose->privacy_system = g_strdup(account->default_privacy_system);
10506 compose_update_privacy_system_menu_item(compose, warn);
10509 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10511 Compose *compose = (Compose *)data;
10513 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10514 gtk_widget_show(compose->ruler_hbox);
10515 prefs_common.show_ruler = TRUE;
10517 gtk_widget_hide(compose->ruler_hbox);
10518 gtk_widget_queue_resize(compose->edit_vbox);
10519 prefs_common.show_ruler = FALSE;
10523 static void compose_attach_drag_received_cb (GtkWidget *widget,
10524 GdkDragContext *context,
10527 GtkSelectionData *data,
10530 gpointer user_data)
10532 Compose *compose = (Compose *)user_data;
10535 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10537 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10539 ) && gtk_drag_get_source_widget(context) !=
10540 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10541 list = uri_list_extract_filenames((const gchar *)data->data);
10542 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10543 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10544 compose_attach_append
10545 (compose, (const gchar *)tmp->data,
10546 utf8_filename, NULL);
10547 g_free(utf8_filename);
10549 if (list) compose_changed_cb(NULL, compose);
10550 list_free_strings(list);
10552 } else if (gtk_drag_get_source_widget(context)
10553 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10554 /* comes from our summaryview */
10555 SummaryView * summaryview = NULL;
10556 GSList * list = NULL, *cur = NULL;
10558 if (mainwindow_get_mainwindow())
10559 summaryview = mainwindow_get_mainwindow()->summaryview;
10562 list = summary_get_selected_msg_list(summaryview);
10564 for (cur = list; cur; cur = cur->next) {
10565 MsgInfo *msginfo = (MsgInfo *)cur->data;
10566 gchar *file = NULL;
10568 file = procmsg_get_message_file_full(msginfo,
10571 compose_attach_append(compose, (const gchar *)file,
10572 (const gchar *)file, "message/rfc822");
10576 g_slist_free(list);
10580 static gboolean compose_drag_drop(GtkWidget *widget,
10581 GdkDragContext *drag_context,
10583 guint time, gpointer user_data)
10585 /* not handling this signal makes compose_insert_drag_received_cb
10590 static void compose_insert_drag_received_cb (GtkWidget *widget,
10591 GdkDragContext *drag_context,
10594 GtkSelectionData *data,
10597 gpointer user_data)
10599 Compose *compose = (Compose *)user_data;
10602 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10605 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10607 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10609 AlertValue val = G_ALERTDEFAULT;
10611 list = uri_list_extract_filenames((const gchar *)data->data);
10612 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10613 /* Assume a list of no files, and data has ://, is a remote link */
10614 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10615 gchar *tmpfile = get_tmp_file();
10616 str_write_to_file(tmpdata, tmpfile);
10618 compose_insert_file(compose, tmpfile);
10619 claws_unlink(tmpfile);
10621 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10622 compose_beautify_paragraph(compose, NULL, TRUE);
10625 switch (prefs_common.compose_dnd_mode) {
10626 case COMPOSE_DND_ASK:
10627 val = alertpanel_full(_("Insert or attach?"),
10628 _("Do you want to insert the contents of the file(s) "
10629 "into the message body, or attach it to the email?"),
10630 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10631 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10633 case COMPOSE_DND_INSERT:
10634 val = G_ALERTALTERNATE;
10636 case COMPOSE_DND_ATTACH:
10637 val = G_ALERTOTHER;
10640 /* unexpected case */
10641 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10644 if (val & G_ALERTDISABLE) {
10645 val &= ~G_ALERTDISABLE;
10646 /* remember what action to perform by default, only if we don't click Cancel */
10647 if (val == G_ALERTALTERNATE)
10648 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10649 else if (val == G_ALERTOTHER)
10650 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10653 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10654 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10655 list_free_strings(list);
10658 } else if (val == G_ALERTOTHER) {
10659 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10660 list_free_strings(list);
10665 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10666 compose_insert_file(compose, (const gchar *)tmp->data);
10668 list_free_strings(list);
10670 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10675 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10678 static void compose_header_drag_received_cb (GtkWidget *widget,
10679 GdkDragContext *drag_context,
10682 GtkSelectionData *data,
10685 gpointer user_data)
10687 GtkEditable *entry = (GtkEditable *)user_data;
10688 gchar *email = (gchar *)data->data;
10690 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10693 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10694 gchar *decoded=g_new(gchar, strlen(email));
10697 email += strlen("mailto:");
10698 decode_uri(decoded, email); /* will fit */
10699 gtk_editable_delete_text(entry, 0, -1);
10700 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10701 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10705 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10708 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10710 Compose *compose = (Compose *)data;
10712 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10713 compose->return_receipt = TRUE;
10715 compose->return_receipt = FALSE;
10718 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10720 Compose *compose = (Compose *)data;
10722 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10723 compose->remove_references = TRUE;
10725 compose->remove_references = FALSE;
10728 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10729 ComposeHeaderEntry *headerentry)
10731 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10735 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10736 GdkEventKey *event,
10737 ComposeHeaderEntry *headerentry)
10739 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10740 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10741 !(event->state & GDK_MODIFIER_MASK) &&
10742 (event->keyval == GDK_BackSpace) &&
10743 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10744 gtk_container_remove
10745 (GTK_CONTAINER(headerentry->compose->header_table),
10746 headerentry->combo);
10747 gtk_container_remove
10748 (GTK_CONTAINER(headerentry->compose->header_table),
10749 headerentry->entry);
10750 headerentry->compose->header_list =
10751 g_slist_remove(headerentry->compose->header_list,
10753 g_free(headerentry);
10754 } else if (event->keyval == GDK_Tab) {
10755 if (headerentry->compose->header_last == headerentry) {
10756 /* Override default next focus, and give it to subject_entry
10757 * instead of notebook tabs
10759 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10760 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10767 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10768 ComposeHeaderEntry *headerentry)
10770 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10771 compose_create_header_entry(headerentry->compose);
10772 g_signal_handlers_disconnect_matched
10773 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10774 0, 0, NULL, NULL, headerentry);
10776 /* Automatically scroll down */
10777 GTK_EVENTS_FLUSH();
10778 compose_show_first_last_header(headerentry->compose, FALSE);
10784 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10786 GtkAdjustment *vadj;
10788 cm_return_if_fail(compose);
10789 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10790 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10791 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10792 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10793 gtk_adjustment_changed(vadj);
10796 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10797 const gchar *text, gint len, Compose *compose)
10799 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10800 (G_OBJECT(compose->text), "paste_as_quotation"));
10803 cm_return_if_fail(text != NULL);
10805 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10806 G_CALLBACK(text_inserted),
10808 if (paste_as_quotation) {
10810 const gchar *qmark;
10812 GtkTextIter start_iter;
10815 len = strlen(text);
10817 new_text = g_strndup(text, len);
10819 qmark = compose_quote_char_from_context(compose);
10821 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10822 gtk_text_buffer_place_cursor(buffer, iter);
10824 pos = gtk_text_iter_get_offset(iter);
10826 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10827 _("Quote format error at line %d."));
10828 quote_fmt_reset_vartable();
10830 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10831 GINT_TO_POINTER(paste_as_quotation - 1));
10833 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10834 gtk_text_buffer_place_cursor(buffer, iter);
10835 gtk_text_buffer_delete_mark(buffer, mark);
10837 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10838 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10839 compose_beautify_paragraph(compose, &start_iter, FALSE);
10840 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10841 gtk_text_buffer_delete_mark(buffer, mark);
10843 if (strcmp(text, "\n") || compose->automatic_break
10844 || gtk_text_iter_starts_line(iter)) {
10845 GtkTextIter before_ins;
10846 gtk_text_buffer_insert(buffer, iter, text, len);
10847 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10848 before_ins = *iter;
10849 gtk_text_iter_backward_chars(&before_ins, len);
10850 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10853 /* check if the preceding is just whitespace or quote */
10854 GtkTextIter start_line;
10855 gchar *tmp = NULL, *quote = NULL;
10856 gint quote_len = 0, is_normal = 0;
10857 start_line = *iter;
10858 gtk_text_iter_set_line_offset(&start_line, 0);
10859 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10862 if (*tmp == '\0') {
10865 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10873 gtk_text_buffer_insert(buffer, iter, text, len);
10875 gtk_text_buffer_insert_with_tags_by_name(buffer,
10876 iter, text, len, "no_join", NULL);
10881 if (!paste_as_quotation) {
10882 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10883 compose_beautify_paragraph(compose, iter, FALSE);
10884 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10885 gtk_text_buffer_delete_mark(buffer, mark);
10888 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10889 G_CALLBACK(text_inserted),
10891 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10893 if (prefs_common.autosave &&
10894 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10895 compose->draft_timeout_tag != -2 /* disabled while loading */)
10896 compose->draft_timeout_tag = g_timeout_add
10897 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10899 static gint compose_defer_auto_save_draft(Compose *compose)
10901 compose->draft_timeout_tag = -1;
10902 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10907 static void compose_check_all(GtkAction *action, gpointer data)
10909 Compose *compose = (Compose *)data;
10910 if (!compose->gtkaspell)
10913 if (gtkut_widget_has_focus(compose->subject_entry))
10914 claws_spell_entry_check_all(
10915 CLAWS_SPELL_ENTRY(compose->subject_entry));
10917 gtkaspell_check_all(compose->gtkaspell);
10920 static void compose_highlight_all(GtkAction *action, gpointer data)
10922 Compose *compose = (Compose *)data;
10923 if (compose->gtkaspell) {
10924 claws_spell_entry_recheck_all(
10925 CLAWS_SPELL_ENTRY(compose->subject_entry));
10926 gtkaspell_highlight_all(compose->gtkaspell);
10930 static void compose_check_backwards(GtkAction *action, gpointer data)
10932 Compose *compose = (Compose *)data;
10933 if (!compose->gtkaspell) {
10934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10938 if (gtkut_widget_has_focus(compose->subject_entry))
10939 claws_spell_entry_check_backwards(
10940 CLAWS_SPELL_ENTRY(compose->subject_entry));
10942 gtkaspell_check_backwards(compose->gtkaspell);
10945 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10947 Compose *compose = (Compose *)data;
10948 if (!compose->gtkaspell) {
10949 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10953 if (gtkut_widget_has_focus(compose->subject_entry))
10954 claws_spell_entry_check_forwards_go(
10955 CLAWS_SPELL_ENTRY(compose->subject_entry));
10957 gtkaspell_check_forwards_go(compose->gtkaspell);
10962 *\brief Guess originating forward account from MsgInfo and several
10963 * "common preference" settings. Return NULL if no guess.
10965 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10967 PrefsAccount *account = NULL;
10969 cm_return_val_if_fail(msginfo, NULL);
10970 cm_return_val_if_fail(msginfo->folder, NULL);
10971 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10973 if (msginfo->folder->prefs->enable_default_account)
10974 account = account_find_from_id(msginfo->folder->prefs->default_account);
10977 account = msginfo->folder->folder->account;
10979 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10981 Xstrdup_a(to, msginfo->to, return NULL);
10982 extract_address(to);
10983 account = account_find_from_address(to, FALSE);
10986 if (!account && prefs_common.forward_account_autosel) {
10987 gchar cc[BUFFSIZE];
10988 if (!procheader_get_header_from_msginfo
10989 (msginfo, cc,sizeof cc , "Cc:")) {
10990 gchar *buf = cc + strlen("Cc:");
10991 extract_address(buf);
10992 account = account_find_from_address(buf, FALSE);
10996 if (!account && prefs_common.forward_account_autosel) {
10997 gchar deliveredto[BUFFSIZE];
10998 if (!procheader_get_header_from_msginfo
10999 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11000 gchar *buf = deliveredto + strlen("Delivered-To:");
11001 extract_address(buf);
11002 account = account_find_from_address(buf, FALSE);
11009 gboolean compose_close(Compose *compose)
11013 if (!g_mutex_trylock(compose->mutex)) {
11014 /* we have to wait for the (possibly deferred by auto-save)
11015 * drafting to be done, before destroying the compose under
11017 debug_print("waiting for drafting to finish...\n");
11018 compose_allow_user_actions(compose, FALSE);
11019 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11022 cm_return_val_if_fail(compose, FALSE);
11023 gtkut_widget_get_uposition(compose->window, &x, &y);
11024 if (!compose->batch) {
11025 prefs_common.compose_x = x;
11026 prefs_common.compose_y = y;
11028 g_mutex_unlock(compose->mutex);
11029 compose_destroy(compose);
11034 * Add entry field for each address in list.
11035 * \param compose E-Mail composition object.
11036 * \param listAddress List of (formatted) E-Mail addresses.
11038 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11041 node = listAddress;
11043 addr = ( gchar * ) node->data;
11044 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11045 node = g_list_next( node );
11049 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11050 guint action, gboolean opening_multiple)
11052 gchar *body = NULL;
11053 GSList *new_msglist = NULL;
11054 MsgInfo *tmp_msginfo = NULL;
11055 gboolean originally_enc = FALSE;
11056 gboolean originally_sig = FALSE;
11057 Compose *compose = NULL;
11058 gchar *s_system = NULL;
11060 cm_return_if_fail(msgview != NULL);
11062 cm_return_if_fail(msginfo_list != NULL);
11064 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11065 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11066 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11068 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11069 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11070 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11071 orig_msginfo, mimeinfo);
11072 if (tmp_msginfo != NULL) {
11073 new_msglist = g_slist_append(NULL, tmp_msginfo);
11075 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11076 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11077 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11079 tmp_msginfo->folder = orig_msginfo->folder;
11080 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11081 if (orig_msginfo->tags) {
11082 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11083 tmp_msginfo->folder->tags_dirty = TRUE;
11089 if (!opening_multiple)
11090 body = messageview_get_selection(msgview);
11093 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11094 procmsg_msginfo_free(tmp_msginfo);
11095 g_slist_free(new_msglist);
11097 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11099 if (compose && originally_enc) {
11100 compose_force_encryption(compose, compose->account, FALSE, s_system);
11103 if (compose && originally_sig && compose->account->default_sign_reply) {
11104 compose_force_signing(compose, compose->account, s_system);
11108 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11111 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11114 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11115 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11116 GSList *cur = msginfo_list;
11117 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11118 "messages. Opening the windows "
11119 "could take some time. Do you "
11120 "want to continue?"),
11121 g_slist_length(msginfo_list));
11122 if (g_slist_length(msginfo_list) > 9
11123 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11124 != G_ALERTALTERNATE) {
11129 /* We'll open multiple compose windows */
11130 /* let the WM place the next windows */
11131 compose_force_window_origin = FALSE;
11132 for (; cur; cur = cur->next) {
11134 tmplist.data = cur->data;
11135 tmplist.next = NULL;
11136 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11138 compose_force_window_origin = TRUE;
11140 /* forwarding multiple mails as attachments is done via a
11141 * single compose window */
11142 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11146 void compose_check_for_email_account(Compose *compose)
11148 PrefsAccount *ac = NULL, *curr = NULL;
11154 if (compose->account && compose->account->protocol == A_NNTP) {
11155 ac = account_get_cur_account();
11156 if (ac->protocol == A_NNTP) {
11157 list = account_get_list();
11159 for( ; list != NULL ; list = g_list_next(list)) {
11160 curr = (PrefsAccount *) list->data;
11161 if (curr->protocol != A_NNTP) {
11167 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11172 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11173 const gchar *address)
11175 GSList *msginfo_list = NULL;
11176 gchar *body = messageview_get_selection(msgview);
11179 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11181 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11182 compose_check_for_email_account(compose);
11183 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11184 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11185 compose_reply_set_subject(compose, msginfo);
11188 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11191 void compose_set_position(Compose *compose, gint pos)
11193 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11195 gtkut_text_view_set_position(text, pos);
11198 gboolean compose_search_string(Compose *compose,
11199 const gchar *str, gboolean case_sens)
11201 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11203 return gtkut_text_view_search_string(text, str, case_sens);
11206 gboolean compose_search_string_backward(Compose *compose,
11207 const gchar *str, gboolean case_sens)
11209 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11211 return gtkut_text_view_search_string_backward(text, str, case_sens);
11214 /* allocate a msginfo structure and populate its data from a compose data structure */
11215 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11217 MsgInfo *newmsginfo;
11219 gchar buf[BUFFSIZE];
11221 cm_return_val_if_fail( compose != NULL, NULL );
11223 newmsginfo = procmsg_msginfo_new();
11226 get_rfc822_date(buf, sizeof(buf));
11227 newmsginfo->date = g_strdup(buf);
11230 if (compose->from_name) {
11231 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11232 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11236 if (compose->subject_entry)
11237 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11239 /* to, cc, reply-to, newsgroups */
11240 for (list = compose->header_list; list; list = list->next) {
11241 gchar *header = gtk_editable_get_chars(
11243 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11244 gchar *entry = gtk_editable_get_chars(
11245 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11247 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11248 if ( newmsginfo->to == NULL ) {
11249 newmsginfo->to = g_strdup(entry);
11250 } else if (entry && *entry) {
11251 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11252 g_free(newmsginfo->to);
11253 newmsginfo->to = tmp;
11256 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11257 if ( newmsginfo->cc == NULL ) {
11258 newmsginfo->cc = g_strdup(entry);
11259 } else if (entry && *entry) {
11260 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11261 g_free(newmsginfo->cc);
11262 newmsginfo->cc = tmp;
11265 if ( strcasecmp(header,
11266 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11267 if ( newmsginfo->newsgroups == NULL ) {
11268 newmsginfo->newsgroups = g_strdup(entry);
11269 } else if (entry && *entry) {
11270 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11271 g_free(newmsginfo->newsgroups);
11272 newmsginfo->newsgroups = tmp;
11280 /* other data is unset */
11286 /* update compose's dictionaries from folder dict settings */
11287 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11288 FolderItem *folder_item)
11290 cm_return_if_fail(compose != NULL);
11292 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11293 FolderItemPrefs *prefs = folder_item->prefs;
11295 if (prefs->enable_default_dictionary)
11296 gtkaspell_change_dict(compose->gtkaspell,
11297 prefs->default_dictionary, FALSE);
11298 if (folder_item->prefs->enable_default_alt_dictionary)
11299 gtkaspell_change_alt_dict(compose->gtkaspell,
11300 prefs->default_alt_dictionary);
11301 if (prefs->enable_default_dictionary
11302 || prefs->enable_default_alt_dictionary)
11303 compose_spell_menu_changed(compose);