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);
3219 compose_entry_append
3221 compose->followup_to ? compose->followup_to :
3222 compose->newsgroups ? compose->newsgroups : "",
3223 COMPOSE_NEWSGROUPS, PREF_NONE);
3225 compose_reply_set_subject(compose, msginfo);
3227 if (to_ml && compose->ml_post) return;
3228 if (!to_all || compose->account->protocol == A_NNTP) return;
3230 if (compose->replyto) {
3231 Xstrdup_a(replyto, compose->replyto, return);
3232 extract_address(replyto);
3234 if (msginfo->from) {
3235 Xstrdup_a(from, msginfo->from, return);
3236 extract_address(from);
3239 if (replyto && from)
3240 cc_list = address_list_append_with_comments(cc_list, from);
3241 if (to_all && msginfo->folder &&
3242 msginfo->folder->prefs->enable_default_reply_to)
3243 cc_list = address_list_append_with_comments(cc_list,
3244 msginfo->folder->prefs->default_reply_to);
3245 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3246 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3248 ac_email = g_utf8_strdown(compose->account->address, -1);
3251 for (cur = cc_list; cur != NULL; cur = cur->next) {
3252 gchar *addr = g_utf8_strdown(cur->data, -1);
3253 extract_address(addr);
3255 if (strcmp(ac_email, addr))
3256 compose_entry_append(compose, (gchar *)cur->data,
3257 COMPOSE_CC, PREF_NONE);
3259 debug_print("Cc address same as compose account's, ignoring\n");
3264 slist_free_strings(cc_list);
3265 g_slist_free(cc_list);
3271 #define SET_ENTRY(entry, str) \
3274 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3277 #define SET_ADDRESS(type, str) \
3280 compose_entry_append(compose, str, type, PREF_NONE); \
3283 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3285 cm_return_if_fail(msginfo != NULL);
3287 SET_ENTRY(subject_entry, msginfo->subject);
3288 SET_ENTRY(from_name, msginfo->from);
3289 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3290 SET_ADDRESS(COMPOSE_CC, compose->cc);
3291 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3292 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3293 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3294 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3296 compose_update_priority_menu_item(compose);
3297 compose_update_privacy_system_menu_item(compose, FALSE);
3298 compose_show_first_last_header(compose, TRUE);
3304 static void compose_insert_sig(Compose *compose, gboolean replace)
3306 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3307 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3309 GtkTextIter iter, iter_end;
3310 gint cur_pos, ins_pos;
3311 gboolean prev_autowrap;
3312 gboolean found = FALSE;
3313 gboolean exists = FALSE;
3315 cm_return_if_fail(compose->account != NULL);
3319 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3320 G_CALLBACK(compose_changed_cb),
3323 mark = gtk_text_buffer_get_insert(buffer);
3324 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3325 cur_pos = gtk_text_iter_get_offset (&iter);
3328 gtk_text_buffer_get_end_iter(buffer, &iter);
3330 exists = (compose->sig_str != NULL);
3333 GtkTextIter first_iter, start_iter, end_iter;
3335 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3337 if (!exists || compose->sig_str[0] == '\0')
3340 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3341 compose->signature_tag);
3344 /* include previous \n\n */
3345 gtk_text_iter_backward_chars(&first_iter, 1);
3346 start_iter = first_iter;
3347 end_iter = first_iter;
3349 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3350 compose->signature_tag);
3351 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3352 compose->signature_tag);
3354 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3360 g_free(compose->sig_str);
3361 compose->sig_str = account_get_signature_str(compose->account);
3363 cur_pos = gtk_text_iter_get_offset(&iter);
3365 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3366 g_free(compose->sig_str);
3367 compose->sig_str = NULL;
3369 if (compose->sig_inserted == FALSE)
3370 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3371 compose->sig_inserted = TRUE;
3373 cur_pos = gtk_text_iter_get_offset(&iter);
3374 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3376 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3377 gtk_text_iter_forward_chars(&iter, 1);
3378 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3379 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3381 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3382 cur_pos = gtk_text_buffer_get_char_count (buffer);
3385 /* put the cursor where it should be
3386 * either where the quote_fmt says, either where it was */
3387 if (compose->set_cursor_pos < 0)
3388 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3390 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3391 compose->set_cursor_pos);
3393 compose->set_cursor_pos = -1;
3394 gtk_text_buffer_place_cursor(buffer, &iter);
3395 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3396 G_CALLBACK(compose_changed_cb),
3402 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3405 GtkTextBuffer *buffer;
3408 const gchar *cur_encoding;
3409 gchar buf[BUFFSIZE];
3412 gboolean prev_autowrap;
3413 gboolean badtxt = FALSE;
3414 struct stat file_stat;
3417 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3419 /* get the size of the file we are about to insert */
3420 ret = g_stat(file, &file_stat);
3422 gchar *shortfile = g_path_get_basename(file);
3423 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3425 return COMPOSE_INSERT_NO_FILE;
3426 } else if (prefs_common.warn_large_insert == TRUE) {
3428 /* ask user for confirmation if the file is large */
3429 if (prefs_common.warn_large_insert_size < 0 ||
3430 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3434 msg = g_strdup_printf(_("You are about to insert a file of %s "
3435 "in the message body. Are you sure you want to do that?"),
3436 to_human_readable(file_stat.st_size));
3437 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3438 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3441 /* do we ask for confirmation next time? */
3442 if (aval & G_ALERTDISABLE) {
3443 /* no confirmation next time, disable feature in preferences */
3444 aval &= ~G_ALERTDISABLE;
3445 prefs_common.warn_large_insert = FALSE;
3448 /* abort file insertion if user canceled action */
3449 if (aval != G_ALERTALTERNATE) {
3450 return COMPOSE_INSERT_NO_FILE;
3456 if ((fp = g_fopen(file, "rb")) == NULL) {
3457 FILE_OP_ERROR(file, "fopen");
3458 return COMPOSE_INSERT_READ_ERROR;
3461 prev_autowrap = compose->autowrap;
3462 compose->autowrap = FALSE;
3464 text = GTK_TEXT_VIEW(compose->text);
3465 buffer = gtk_text_view_get_buffer(text);
3466 mark = gtk_text_buffer_get_insert(buffer);
3467 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3469 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3470 G_CALLBACK(text_inserted),
3473 cur_encoding = conv_get_locale_charset_str_no_utf8();
3475 while (fgets(buf, sizeof(buf), fp) != NULL) {
3478 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3479 str = g_strdup(buf);
3481 str = conv_codeset_strdup
3482 (buf, cur_encoding, CS_INTERNAL);
3485 /* strip <CR> if DOS/Windows file,
3486 replace <CR> with <LF> if Macintosh file. */
3489 if (len > 0 && str[len - 1] != '\n') {
3491 if (str[len] == '\r') str[len] = '\n';
3494 gtk_text_buffer_insert(buffer, &iter, str, -1);
3498 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3499 G_CALLBACK(text_inserted),
3501 compose->autowrap = prev_autowrap;
3502 if (compose->autowrap)
3503 compose_wrap_all(compose);
3508 return COMPOSE_INSERT_INVALID_CHARACTER;
3510 return COMPOSE_INSERT_SUCCESS;
3513 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3514 const gchar *filename,
3515 const gchar *content_type)
3523 GtkListStore *store;
3525 gboolean has_binary = FALSE;
3527 if (!is_file_exist(file)) {
3528 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3529 gboolean result = FALSE;
3530 if (file_from_uri && is_file_exist(file_from_uri)) {
3531 result = compose_attach_append(
3532 compose, file_from_uri,
3536 g_free(file_from_uri);
3539 alertpanel_error("File %s doesn't exist\n", filename);
3542 if ((size = get_file_size(file)) < 0) {
3543 alertpanel_error("Can't get file size of %s\n", filename);
3547 alertpanel_error(_("File %s is empty."), filename);
3550 if ((fp = g_fopen(file, "rb")) == NULL) {
3551 alertpanel_error(_("Can't read %s."), filename);
3556 ainfo = g_new0(AttachInfo, 1);
3557 auto_ainfo = g_auto_pointer_new_with_free
3558 (ainfo, (GFreeFunc) compose_attach_info_free);
3559 ainfo->file = g_strdup(file);
3562 ainfo->content_type = g_strdup(content_type);
3563 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3565 MsgFlags flags = {0, 0};
3567 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3568 ainfo->encoding = ENC_7BIT;
3570 ainfo->encoding = ENC_8BIT;
3572 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3573 if (msginfo && msginfo->subject)
3574 name = g_strdup(msginfo->subject);
3576 name = g_path_get_basename(filename ? filename : file);
3578 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3580 procmsg_msginfo_free(msginfo);
3582 if (!g_ascii_strncasecmp(content_type, "text", 4))
3583 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3585 ainfo->encoding = ENC_BASE64;
3586 name = g_path_get_basename(filename ? filename : file);
3587 ainfo->name = g_strdup(name);
3591 ainfo->content_type = procmime_get_mime_type(file);
3592 if (!ainfo->content_type) {
3593 ainfo->content_type =
3594 g_strdup("application/octet-stream");
3595 ainfo->encoding = ENC_BASE64;
3596 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3598 procmime_get_encoding_for_text_file(file, &has_binary);
3600 ainfo->encoding = ENC_BASE64;
3601 name = g_path_get_basename(filename ? filename : file);
3602 ainfo->name = g_strdup(name);
3606 if (ainfo->name != NULL
3607 && !strcmp(ainfo->name, ".")) {
3608 g_free(ainfo->name);
3612 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3613 g_free(ainfo->content_type);
3614 ainfo->content_type = g_strdup("application/octet-stream");
3617 ainfo->size = (goffset)size;
3618 size_text = to_human_readable((goffset)size);
3620 store = GTK_LIST_STORE(gtk_tree_view_get_model
3621 (GTK_TREE_VIEW(compose->attach_clist)));
3623 gtk_list_store_append(store, &iter);
3624 gtk_list_store_set(store, &iter,
3625 COL_MIMETYPE, ainfo->content_type,
3626 COL_SIZE, size_text,
3627 COL_NAME, ainfo->name,
3629 COL_AUTODATA, auto_ainfo,
3632 g_auto_pointer_free(auto_ainfo);
3633 compose_attach_update_label(compose);
3637 static void compose_use_signing(Compose *compose, gboolean use_signing)
3639 compose->use_signing = use_signing;
3640 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3643 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3645 compose->use_encryption = use_encryption;
3646 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3649 #define NEXT_PART_NOT_CHILD(info) \
3651 node = info->node; \
3652 while (node->children) \
3653 node = g_node_last_child(node); \
3654 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3657 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3661 MimeInfo *firsttext = NULL;
3662 MimeInfo *encrypted = NULL;
3665 const gchar *partname = NULL;
3667 mimeinfo = procmime_scan_message(msginfo);
3668 if (!mimeinfo) return;
3670 if (mimeinfo->node->children == NULL) {
3671 procmime_mimeinfo_free_all(mimeinfo);
3675 /* find first content part */
3676 child = (MimeInfo *) mimeinfo->node->children->data;
3677 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3678 child = (MimeInfo *)child->node->children->data;
3681 if (child->type == MIMETYPE_TEXT) {
3683 debug_print("First text part found\n");
3684 } else if (compose->mode == COMPOSE_REEDIT &&
3685 child->type == MIMETYPE_APPLICATION &&
3686 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3687 encrypted = (MimeInfo *)child->node->parent->data;
3690 child = (MimeInfo *) mimeinfo->node->children->data;
3691 while (child != NULL) {
3694 if (child == encrypted) {
3695 /* skip this part of tree */
3696 NEXT_PART_NOT_CHILD(child);
3700 if (child->type == MIMETYPE_MULTIPART) {
3701 /* get the actual content */
3702 child = procmime_mimeinfo_next(child);
3706 if (child == firsttext) {
3707 child = procmime_mimeinfo_next(child);
3711 outfile = procmime_get_tmp_file_name(child);
3712 if ((err = procmime_get_part(outfile, child)) < 0)
3713 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3715 gchar *content_type;
3717 content_type = procmime_get_content_type_str(child->type, child->subtype);
3719 /* if we meet a pgp signature, we don't attach it, but
3720 * we force signing. */
3721 if ((strcmp(content_type, "application/pgp-signature") &&
3722 strcmp(content_type, "application/pkcs7-signature") &&
3723 strcmp(content_type, "application/x-pkcs7-signature"))
3724 || compose->mode == COMPOSE_REDIRECT) {
3725 partname = procmime_mimeinfo_get_parameter(child, "filename");
3726 if (partname == NULL)
3727 partname = procmime_mimeinfo_get_parameter(child, "name");
3728 if (partname == NULL)
3730 compose_attach_append(compose, outfile,
3731 partname, content_type);
3733 compose_force_signing(compose, compose->account, NULL);
3735 g_free(content_type);
3738 NEXT_PART_NOT_CHILD(child);
3740 procmime_mimeinfo_free_all(mimeinfo);
3743 #undef NEXT_PART_NOT_CHILD
3748 WAIT_FOR_INDENT_CHAR,
3749 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3752 /* return indent length, we allow:
3753 indent characters followed by indent characters or spaces/tabs,
3754 alphabets and numbers immediately followed by indent characters,
3755 and the repeating sequences of the above
3756 If quote ends with multiple spaces, only the first one is included. */
3757 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3758 const GtkTextIter *start, gint *len)
3760 GtkTextIter iter = *start;
3764 IndentState state = WAIT_FOR_INDENT_CHAR;
3767 gint alnum_count = 0;
3768 gint space_count = 0;
3771 if (prefs_common.quote_chars == NULL) {
3775 while (!gtk_text_iter_ends_line(&iter)) {
3776 wc = gtk_text_iter_get_char(&iter);
3777 if (g_unichar_iswide(wc))
3779 clen = g_unichar_to_utf8(wc, ch);
3783 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3784 is_space = g_unichar_isspace(wc);
3786 if (state == WAIT_FOR_INDENT_CHAR) {
3787 if (!is_indent && !g_unichar_isalnum(wc))
3790 quote_len += alnum_count + space_count + 1;
3791 alnum_count = space_count = 0;
3792 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3795 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3796 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3800 else if (is_indent) {
3801 quote_len += alnum_count + space_count + 1;
3802 alnum_count = space_count = 0;
3805 state = WAIT_FOR_INDENT_CHAR;
3809 gtk_text_iter_forward_char(&iter);
3812 if (quote_len > 0 && space_count > 0)
3818 if (quote_len > 0) {
3820 gtk_text_iter_forward_chars(&iter, quote_len);
3821 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3827 /* return >0 if the line is itemized */
3828 static int compose_itemized_length(GtkTextBuffer *buffer,
3829 const GtkTextIter *start)
3831 GtkTextIter iter = *start;
3836 if (gtk_text_iter_ends_line(&iter))
3841 wc = gtk_text_iter_get_char(&iter);
3842 if (!g_unichar_isspace(wc))
3844 gtk_text_iter_forward_char(&iter);
3845 if (gtk_text_iter_ends_line(&iter))
3849 clen = g_unichar_to_utf8(wc, ch);
3853 if (!strchr("*-+", ch[0]))
3856 gtk_text_iter_forward_char(&iter);
3857 if (gtk_text_iter_ends_line(&iter))
3859 wc = gtk_text_iter_get_char(&iter);
3860 if (g_unichar_isspace(wc)) {
3866 /* return the string at the start of the itemization */
3867 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3868 const GtkTextIter *start)
3870 GtkTextIter iter = *start;
3873 GString *item_chars = g_string_new("");
3876 if (gtk_text_iter_ends_line(&iter))
3881 wc = gtk_text_iter_get_char(&iter);
3882 if (!g_unichar_isspace(wc))
3884 gtk_text_iter_forward_char(&iter);
3885 if (gtk_text_iter_ends_line(&iter))
3887 g_string_append_unichar(item_chars, wc);
3890 str = item_chars->str;
3891 g_string_free(item_chars, FALSE);
3895 /* return the number of spaces at a line's start */
3896 static int compose_left_offset_length(GtkTextBuffer *buffer,
3897 const GtkTextIter *start)
3899 GtkTextIter iter = *start;
3902 if (gtk_text_iter_ends_line(&iter))
3906 wc = gtk_text_iter_get_char(&iter);
3907 if (!g_unichar_isspace(wc))
3910 gtk_text_iter_forward_char(&iter);
3911 if (gtk_text_iter_ends_line(&iter))
3915 gtk_text_iter_forward_char(&iter);
3916 if (gtk_text_iter_ends_line(&iter))
3921 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3922 const GtkTextIter *start,
3923 GtkTextIter *break_pos,
3927 GtkTextIter iter = *start, line_end = *start;
3928 PangoLogAttr *attrs;
3935 gboolean can_break = FALSE;
3936 gboolean do_break = FALSE;
3937 gboolean was_white = FALSE;
3938 gboolean prev_dont_break = FALSE;
3940 gtk_text_iter_forward_to_line_end(&line_end);
3941 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3942 len = g_utf8_strlen(str, -1);
3946 g_warning("compose_get_line_break_pos: len = 0!\n");
3950 /* g_print("breaking line: %d: %s (len = %d)\n",
3951 gtk_text_iter_get_line(&iter), str, len); */
3953 attrs = g_new(PangoLogAttr, len + 1);
3955 pango_default_break(str, -1, NULL, attrs, len + 1);
3959 /* skip quote and leading spaces */
3960 for (i = 0; *p != '\0' && i < len; i++) {
3963 wc = g_utf8_get_char(p);
3964 if (i >= quote_len && !g_unichar_isspace(wc))
3966 if (g_unichar_iswide(wc))
3968 else if (*p == '\t')
3972 p = g_utf8_next_char(p);
3975 for (; *p != '\0' && i < len; i++) {
3976 PangoLogAttr *attr = attrs + i;
3980 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3983 was_white = attr->is_white;
3985 /* don't wrap URI */
3986 if ((uri_len = get_uri_len(p)) > 0) {
3988 if (pos > 0 && col > max_col) {
3998 wc = g_utf8_get_char(p);
3999 if (g_unichar_iswide(wc)) {
4001 if (prev_dont_break && can_break && attr->is_line_break)
4003 } else if (*p == '\t')
4007 if (pos > 0 && col > max_col) {
4012 if (*p == '-' || *p == '/')
4013 prev_dont_break = TRUE;
4015 prev_dont_break = FALSE;
4017 p = g_utf8_next_char(p);
4021 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4026 *break_pos = *start;
4027 gtk_text_iter_set_line_offset(break_pos, pos);
4032 static gboolean compose_join_next_line(Compose *compose,
4033 GtkTextBuffer *buffer,
4035 const gchar *quote_str)
4037 GtkTextIter iter_ = *iter, cur, prev, next, end;
4038 PangoLogAttr attrs[3];
4040 gchar *next_quote_str;
4043 gboolean keep_cursor = FALSE;
4045 if (!gtk_text_iter_forward_line(&iter_) ||
4046 gtk_text_iter_ends_line(&iter_)) {
4049 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4051 if ((quote_str || next_quote_str) &&
4052 strcmp2(quote_str, next_quote_str) != 0) {
4053 g_free(next_quote_str);
4056 g_free(next_quote_str);
4059 if (quote_len > 0) {
4060 gtk_text_iter_forward_chars(&end, quote_len);
4061 if (gtk_text_iter_ends_line(&end)) {
4066 /* don't join itemized lines */
4067 if (compose_itemized_length(buffer, &end) > 0) {
4071 /* don't join signature separator */
4072 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4075 /* delete quote str */
4077 gtk_text_buffer_delete(buffer, &iter_, &end);
4079 /* don't join line breaks put by the user */
4081 gtk_text_iter_backward_char(&cur);
4082 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4083 gtk_text_iter_forward_char(&cur);
4087 gtk_text_iter_forward_char(&cur);
4088 /* delete linebreak and extra spaces */
4089 while (gtk_text_iter_backward_char(&cur)) {
4090 wc1 = gtk_text_iter_get_char(&cur);
4091 if (!g_unichar_isspace(wc1))
4096 while (!gtk_text_iter_ends_line(&cur)) {
4097 wc1 = gtk_text_iter_get_char(&cur);
4098 if (!g_unichar_isspace(wc1))
4100 gtk_text_iter_forward_char(&cur);
4103 if (!gtk_text_iter_equal(&prev, &next)) {
4106 mark = gtk_text_buffer_get_insert(buffer);
4107 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4108 if (gtk_text_iter_equal(&prev, &cur))
4110 gtk_text_buffer_delete(buffer, &prev, &next);
4114 /* insert space if required */
4115 gtk_text_iter_backward_char(&prev);
4116 wc1 = gtk_text_iter_get_char(&prev);
4117 wc2 = gtk_text_iter_get_char(&next);
4118 gtk_text_iter_forward_char(&next);
4119 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4120 pango_default_break(str, -1, NULL, attrs, 3);
4121 if (!attrs[1].is_line_break ||
4122 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4123 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4125 gtk_text_iter_backward_char(&iter_);
4126 gtk_text_buffer_place_cursor(buffer, &iter_);
4135 #define ADD_TXT_POS(bp_, ep_, pti_) \
4136 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4137 last = last->next; \
4138 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4139 last->next = NULL; \
4141 g_warning("alloc error scanning URIs\n"); \
4144 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4146 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4147 GtkTextBuffer *buffer;
4148 GtkTextIter iter, break_pos, end_of_line;
4149 gchar *quote_str = NULL;
4151 gboolean wrap_quote = prefs_common.linewrap_quote;
4152 gboolean prev_autowrap = compose->autowrap;
4153 gint startq_offset = -1, noq_offset = -1;
4154 gint uri_start = -1, uri_stop = -1;
4155 gint nouri_start = -1, nouri_stop = -1;
4156 gint num_blocks = 0;
4157 gint quotelevel = -1;
4158 gboolean modified = force;
4159 gboolean removed = FALSE;
4160 gboolean modified_before_remove = FALSE;
4162 gboolean start = TRUE;
4163 gint itemized_len = 0, rem_item_len = 0;
4164 gchar *itemized_chars = NULL;
4165 gboolean item_continuation = FALSE;
4170 if (compose->draft_timeout_tag == -2) {
4174 compose->autowrap = FALSE;
4176 buffer = gtk_text_view_get_buffer(text);
4177 undo_wrapping(compose->undostruct, TRUE);
4182 mark = gtk_text_buffer_get_insert(buffer);
4183 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4187 if (compose->draft_timeout_tag == -2) {
4188 if (gtk_text_iter_ends_line(&iter)) {
4189 while (gtk_text_iter_ends_line(&iter) &&
4190 gtk_text_iter_forward_line(&iter))
4193 while (gtk_text_iter_backward_line(&iter)) {
4194 if (gtk_text_iter_ends_line(&iter)) {
4195 gtk_text_iter_forward_line(&iter);
4201 /* move to line start */
4202 gtk_text_iter_set_line_offset(&iter, 0);
4205 itemized_len = compose_itemized_length(buffer, &iter);
4207 if (!itemized_len) {
4208 itemized_len = compose_left_offset_length(buffer, &iter);
4209 item_continuation = TRUE;
4213 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4215 /* go until paragraph end (empty line) */
4216 while (start || !gtk_text_iter_ends_line(&iter)) {
4217 gchar *scanpos = NULL;
4218 /* parse table - in order of priority */
4220 const gchar *needle; /* token */
4222 /* token search function */
4223 gchar *(*search) (const gchar *haystack,
4224 const gchar *needle);
4225 /* part parsing function */
4226 gboolean (*parse) (const gchar *start,
4227 const gchar *scanpos,
4231 /* part to URI function */
4232 gchar *(*build_uri) (const gchar *bp,
4236 static struct table parser[] = {
4237 {"http://", strcasestr, get_uri_part, make_uri_string},
4238 {"https://", strcasestr, get_uri_part, make_uri_string},
4239 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4240 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4241 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4242 {"www.", strcasestr, get_uri_part, make_http_string},
4243 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4244 {"@", strcasestr, get_email_part, make_email_string}
4246 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4247 gint last_index = PARSE_ELEMS;
4249 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4253 if (!prev_autowrap && num_blocks == 0) {
4255 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4256 G_CALLBACK(text_inserted),
4259 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4262 uri_start = uri_stop = -1;
4264 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4267 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4268 if (startq_offset == -1)
4269 startq_offset = gtk_text_iter_get_offset(&iter);
4270 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4271 if (quotelevel > 2) {
4272 /* recycle colors */
4273 if (prefs_common.recycle_quote_colors)
4282 if (startq_offset == -1)
4283 noq_offset = gtk_text_iter_get_offset(&iter);
4287 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4290 if (gtk_text_iter_ends_line(&iter)) {
4292 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4293 prefs_common.linewrap_len,
4295 GtkTextIter prev, next, cur;
4296 if (prev_autowrap != FALSE || force) {
4297 compose->automatic_break = TRUE;
4299 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4300 compose->automatic_break = FALSE;
4301 if (itemized_len && compose->autoindent) {
4302 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4303 if (!item_continuation)
4304 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4306 } else if (quote_str && wrap_quote) {
4307 compose->automatic_break = TRUE;
4309 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4310 compose->automatic_break = FALSE;
4311 if (itemized_len && compose->autoindent) {
4312 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4313 if (!item_continuation)
4314 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4318 /* remove trailing spaces */
4320 rem_item_len = itemized_len;
4321 while (compose->autoindent && rem_item_len-- > 0)
4322 gtk_text_iter_backward_char(&cur);
4323 gtk_text_iter_backward_char(&cur);
4326 while (!gtk_text_iter_starts_line(&cur)) {
4329 gtk_text_iter_backward_char(&cur);
4330 wc = gtk_text_iter_get_char(&cur);
4331 if (!g_unichar_isspace(wc))
4335 if (!gtk_text_iter_equal(&prev, &next)) {
4336 gtk_text_buffer_delete(buffer, &prev, &next);
4338 gtk_text_iter_forward_char(&break_pos);
4342 gtk_text_buffer_insert(buffer, &break_pos,
4346 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4348 /* move iter to current line start */
4349 gtk_text_iter_set_line_offset(&iter, 0);
4356 /* move iter to next line start */
4362 if (!prev_autowrap && num_blocks > 0) {
4364 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4365 G_CALLBACK(text_inserted),
4369 while (!gtk_text_iter_ends_line(&end_of_line)) {
4370 gtk_text_iter_forward_char(&end_of_line);
4372 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4374 nouri_start = gtk_text_iter_get_offset(&iter);
4375 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4377 walk_pos = gtk_text_iter_get_offset(&iter);
4378 /* FIXME: this looks phony. scanning for anything in the parse table */
4379 for (n = 0; n < PARSE_ELEMS; n++) {
4382 tmp = parser[n].search(walk, parser[n].needle);
4384 if (scanpos == NULL || tmp < scanpos) {
4393 /* check if URI can be parsed */
4394 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4395 (const gchar **)&ep, FALSE)
4396 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4400 strlen(parser[last_index].needle);
4403 uri_start = walk_pos + (bp - o_walk);
4404 uri_stop = walk_pos + (ep - o_walk);
4408 gtk_text_iter_forward_line(&iter);
4411 if (startq_offset != -1) {
4412 GtkTextIter startquote, endquote;
4413 gtk_text_buffer_get_iter_at_offset(
4414 buffer, &startquote, startq_offset);
4417 switch (quotelevel) {
4419 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4420 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4421 gtk_text_buffer_apply_tag_by_name(
4422 buffer, "quote0", &startquote, &endquote);
4423 gtk_text_buffer_remove_tag_by_name(
4424 buffer, "quote1", &startquote, &endquote);
4425 gtk_text_buffer_remove_tag_by_name(
4426 buffer, "quote2", &startquote, &endquote);
4431 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4432 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4433 gtk_text_buffer_apply_tag_by_name(
4434 buffer, "quote1", &startquote, &endquote);
4435 gtk_text_buffer_remove_tag_by_name(
4436 buffer, "quote0", &startquote, &endquote);
4437 gtk_text_buffer_remove_tag_by_name(
4438 buffer, "quote2", &startquote, &endquote);
4443 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4444 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4445 gtk_text_buffer_apply_tag_by_name(
4446 buffer, "quote2", &startquote, &endquote);
4447 gtk_text_buffer_remove_tag_by_name(
4448 buffer, "quote0", &startquote, &endquote);
4449 gtk_text_buffer_remove_tag_by_name(
4450 buffer, "quote1", &startquote, &endquote);
4456 } else if (noq_offset != -1) {
4457 GtkTextIter startnoquote, endnoquote;
4458 gtk_text_buffer_get_iter_at_offset(
4459 buffer, &startnoquote, noq_offset);
4462 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4463 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4464 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4465 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4466 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4467 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4468 gtk_text_buffer_remove_tag_by_name(
4469 buffer, "quote0", &startnoquote, &endnoquote);
4470 gtk_text_buffer_remove_tag_by_name(
4471 buffer, "quote1", &startnoquote, &endnoquote);
4472 gtk_text_buffer_remove_tag_by_name(
4473 buffer, "quote2", &startnoquote, &endnoquote);
4479 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4480 GtkTextIter nouri_start_iter, nouri_end_iter;
4481 gtk_text_buffer_get_iter_at_offset(
4482 buffer, &nouri_start_iter, nouri_start);
4483 gtk_text_buffer_get_iter_at_offset(
4484 buffer, &nouri_end_iter, nouri_stop);
4485 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4486 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4487 gtk_text_buffer_remove_tag_by_name(
4488 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4489 modified_before_remove = modified;
4494 if (uri_start >= 0 && uri_stop > 0) {
4495 GtkTextIter uri_start_iter, uri_end_iter, back;
4496 gtk_text_buffer_get_iter_at_offset(
4497 buffer, &uri_start_iter, uri_start);
4498 gtk_text_buffer_get_iter_at_offset(
4499 buffer, &uri_end_iter, uri_stop);
4500 back = uri_end_iter;
4501 gtk_text_iter_backward_char(&back);
4502 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4503 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4504 gtk_text_buffer_apply_tag_by_name(
4505 buffer, "link", &uri_start_iter, &uri_end_iter);
4507 if (removed && !modified_before_remove) {
4513 // debug_print("not modified, out after %d lines\n", lines);
4517 // debug_print("modified, out after %d lines\n", lines);
4519 g_free(itemized_chars);
4522 undo_wrapping(compose->undostruct, FALSE);
4523 compose->autowrap = prev_autowrap;
4528 void compose_action_cb(void *data)
4530 Compose *compose = (Compose *)data;
4531 compose_wrap_all(compose);
4534 static void compose_wrap_all(Compose *compose)
4536 compose_wrap_all_full(compose, FALSE);
4539 static void compose_wrap_all_full(Compose *compose, gboolean force)
4541 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4542 GtkTextBuffer *buffer;
4544 gboolean modified = TRUE;
4546 buffer = gtk_text_view_get_buffer(text);
4548 gtk_text_buffer_get_start_iter(buffer, &iter);
4549 while (!gtk_text_iter_is_end(&iter) && modified)
4550 modified = compose_beautify_paragraph(compose, &iter, force);
4554 static void compose_set_title(Compose *compose)
4560 edited = compose->modified ? _(" [Edited]") : "";
4562 subject = gtk_editable_get_chars(
4563 GTK_EDITABLE(compose->subject_entry), 0, -1);
4565 #ifndef GENERIC_UMPC
4566 if (subject && strlen(subject))
4567 str = g_strdup_printf(_("%s - Compose message%s"),
4570 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4572 str = g_strdup(_("Compose message"));
4575 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4581 * compose_current_mail_account:
4583 * Find a current mail account (the currently selected account, or the
4584 * default account, if a news account is currently selected). If a
4585 * mail account cannot be found, display an error message.
4587 * Return value: Mail account, or NULL if not found.
4589 static PrefsAccount *
4590 compose_current_mail_account(void)
4594 if (cur_account && cur_account->protocol != A_NNTP)
4597 ac = account_get_default();
4598 if (!ac || ac->protocol == A_NNTP) {
4599 alertpanel_error(_("Account for sending mail is not specified.\n"
4600 "Please select a mail account before sending."));
4607 #define QUOTE_IF_REQUIRED(out, str) \
4609 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4613 len = strlen(str) + 3; \
4614 if ((__tmp = alloca(len)) == NULL) { \
4615 g_warning("can't allocate memory\n"); \
4616 g_string_free(header, TRUE); \
4619 g_snprintf(__tmp, len, "\"%s\"", str); \
4624 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4625 g_warning("can't allocate memory\n"); \
4626 g_string_free(header, TRUE); \
4629 strcpy(__tmp, str); \
4635 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4637 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4641 len = strlen(str) + 3; \
4642 if ((__tmp = alloca(len)) == NULL) { \
4643 g_warning("can't allocate memory\n"); \
4646 g_snprintf(__tmp, len, "\"%s\"", str); \
4651 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4652 g_warning("can't allocate memory\n"); \
4655 strcpy(__tmp, str); \
4661 static void compose_select_account(Compose *compose, PrefsAccount *account,
4664 gchar *from = NULL, *header;
4665 ComposeHeaderEntry *header_entry;
4667 cm_return_if_fail(account != NULL);
4669 compose->account = account;
4670 if (account->name && *account->name) {
4672 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4673 from = g_strdup_printf("%s <%s>",
4674 buf, account->address);
4675 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4677 from = g_strdup_printf("<%s>",
4679 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4684 compose_set_title(compose);
4686 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4687 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4689 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4690 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4691 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4693 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4695 activate_privacy_system(compose, account, FALSE);
4697 if (!init && compose->mode != COMPOSE_REDIRECT) {
4698 undo_block(compose->undostruct);
4699 compose_insert_sig(compose, TRUE);
4700 undo_unblock(compose->undostruct);
4703 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4704 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4706 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4707 if (account->protocol == A_NNTP) {
4708 if (!strcmp(header, _("To:")))
4709 combobox_select_by_text(
4710 GTK_COMBO_BOX(header_entry->combo),
4713 if (!strcmp(header, _("Newsgroups:")))
4714 combobox_select_by_text(
4715 GTK_COMBO_BOX(header_entry->combo),
4723 /* use account's dict info if set */
4724 if (compose->gtkaspell) {
4725 if (account->enable_default_dictionary)
4726 gtkaspell_change_dict(compose->gtkaspell,
4727 account->default_dictionary, FALSE);
4728 if (account->enable_default_alt_dictionary)
4729 gtkaspell_change_alt_dict(compose->gtkaspell,
4730 account->default_alt_dictionary);
4731 if (account->enable_default_dictionary
4732 || account->enable_default_alt_dictionary)
4733 compose_spell_menu_changed(compose);
4738 gboolean compose_check_for_valid_recipient(Compose *compose) {
4739 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4740 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4741 gboolean recipient_found = FALSE;
4745 /* free to and newsgroup list */
4746 slist_free_strings(compose->to_list);
4747 g_slist_free(compose->to_list);
4748 compose->to_list = NULL;
4750 slist_free_strings(compose->newsgroup_list);
4751 g_slist_free(compose->newsgroup_list);
4752 compose->newsgroup_list = NULL;
4754 /* search header entries for to and newsgroup entries */
4755 for (list = compose->header_list; list; list = list->next) {
4758 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4759 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4762 if (entry[0] != '\0') {
4763 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4764 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4765 compose->to_list = address_list_append(compose->to_list, entry);
4766 recipient_found = TRUE;
4769 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4770 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4771 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4772 recipient_found = TRUE;
4779 return recipient_found;
4782 static gboolean compose_check_for_set_recipients(Compose *compose)
4784 if (compose->account->set_autocc && compose->account->auto_cc) {
4785 gboolean found_other = FALSE;
4787 /* search header entries for to and newsgroup entries */
4788 for (list = compose->header_list; list; list = list->next) {
4791 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4792 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4795 if (strcmp(entry, compose->account->auto_cc)
4796 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4806 if (compose->batch) {
4807 gtk_widget_show_all(compose->window);
4809 aval = alertpanel(_("Send"),
4810 _("The only recipient is the default CC address. Send anyway?"),
4811 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4812 if (aval != G_ALERTALTERNATE)
4816 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4817 gboolean found_other = FALSE;
4819 /* search header entries for to and newsgroup entries */
4820 for (list = compose->header_list; list; list = list->next) {
4823 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4824 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4827 if (strcmp(entry, compose->account->auto_bcc)
4828 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4838 if (compose->batch) {
4839 gtk_widget_show_all(compose->window);
4841 aval = alertpanel(_("Send"),
4842 _("The only recipient is the default BCC address. Send anyway?"),
4843 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4844 if (aval != G_ALERTALTERNATE)
4851 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4855 if (compose_check_for_valid_recipient(compose) == FALSE) {
4856 if (compose->batch) {
4857 gtk_widget_show_all(compose->window);
4859 alertpanel_error(_("Recipient is not specified."));
4863 if (compose_check_for_set_recipients(compose) == FALSE) {
4867 if (!compose->batch) {
4868 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4869 if (*str == '\0' && check_everything == TRUE &&
4870 compose->mode != COMPOSE_REDIRECT) {
4872 gchar *button_label;
4875 if (compose->sending)
4876 button_label = _("+_Send");
4878 button_label = _("+_Queue");
4879 message = g_strdup_printf(_("Subject is empty. %s"),
4880 compose->sending?_("Send it anyway?"):
4881 _("Queue it anyway?"));
4883 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4884 GTK_STOCK_CANCEL, button_label, NULL);
4886 if (aval != G_ALERTALTERNATE)
4891 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4897 gint compose_send(Compose *compose)
4900 FolderItem *folder = NULL;
4902 gchar *msgpath = NULL;
4903 gboolean discard_window = FALSE;
4904 gchar *errstr = NULL;
4905 gchar *tmsgid = NULL;
4906 MainWindow *mainwin = mainwindow_get_mainwindow();
4907 gboolean queued_removed = FALSE;
4909 if (prefs_common.send_dialog_invisible
4910 || compose->batch == TRUE)
4911 discard_window = TRUE;
4913 compose_allow_user_actions (compose, FALSE);
4914 compose->sending = TRUE;
4916 if (compose_check_entries(compose, TRUE) == FALSE) {
4917 if (compose->batch) {
4918 gtk_widget_show_all(compose->window);
4924 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4927 if (compose->batch) {
4928 gtk_widget_show_all(compose->window);
4931 alertpanel_error(_("Could not queue message for sending:\n\n"
4932 "Charset conversion failed."));
4933 } else if (val == -5) {
4934 alertpanel_error(_("Could not queue message for sending:\n\n"
4935 "Couldn't get recipient encryption key."));
4936 } else if (val == -6) {
4938 } else if (val == -3) {
4939 if (privacy_peek_error())
4940 alertpanel_error(_("Could not queue message for sending:\n\n"
4941 "Signature failed: %s"), privacy_get_error());
4942 } else if (val == -2 && errno != 0) {
4943 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4945 alertpanel_error(_("Could not queue message for sending."));
4950 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4951 if (discard_window) {
4952 compose->sending = FALSE;
4953 compose_close(compose);
4954 /* No more compose access in the normal codepath
4955 * after this point! */
4960 alertpanel_error(_("The message was queued but could not be "
4961 "sent.\nUse \"Send queued messages\" from "
4962 "the main window to retry."));
4963 if (!discard_window) {
4970 if (msgpath == NULL) {
4971 msgpath = folder_item_fetch_msg(folder, msgnum);
4972 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4975 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4976 claws_unlink(msgpath);
4979 if (!discard_window) {
4981 if (!queued_removed)
4982 folder_item_remove_msg(folder, msgnum);
4983 folder_item_scan(folder);
4985 /* make sure we delete that */
4986 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4988 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4989 folder_item_remove_msg(folder, tmp->msgnum);
4990 procmsg_msginfo_free(tmp);
4997 if (!queued_removed)
4998 folder_item_remove_msg(folder, msgnum);
4999 folder_item_scan(folder);
5001 /* make sure we delete that */
5002 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5004 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5005 folder_item_remove_msg(folder, tmp->msgnum);
5006 procmsg_msginfo_free(tmp);
5009 if (!discard_window) {
5010 compose->sending = FALSE;
5011 compose_allow_user_actions (compose, TRUE);
5012 compose_close(compose);
5016 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5017 "the main window to retry."), errstr);
5020 alertpanel_error_log(_("The message was queued but could not be "
5021 "sent.\nUse \"Send queued messages\" from "
5022 "the main window to retry."));
5024 if (!discard_window) {
5033 toolbar_main_set_sensitive(mainwin);
5034 main_window_set_menu_sensitive(mainwin);
5040 compose_allow_user_actions (compose, TRUE);
5041 compose->sending = FALSE;
5042 compose->modified = TRUE;
5043 toolbar_main_set_sensitive(mainwin);
5044 main_window_set_menu_sensitive(mainwin);
5049 static gboolean compose_use_attach(Compose *compose)
5051 GtkTreeModel *model = gtk_tree_view_get_model
5052 (GTK_TREE_VIEW(compose->attach_clist));
5053 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5056 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5059 gchar buf[BUFFSIZE];
5061 gboolean first_to_address;
5062 gboolean first_cc_address;
5064 ComposeHeaderEntry *headerentry;
5065 const gchar *headerentryname;
5066 const gchar *cc_hdr;
5067 const gchar *to_hdr;
5068 gboolean err = FALSE;
5070 debug_print("Writing redirect header\n");
5072 cc_hdr = prefs_common_translated_header_name("Cc:");
5073 to_hdr = prefs_common_translated_header_name("To:");
5075 first_to_address = TRUE;
5076 for (list = compose->header_list; list; list = list->next) {
5077 headerentry = ((ComposeHeaderEntry *)list->data);
5078 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5080 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5081 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5082 Xstrdup_a(str, entstr, return -1);
5084 if (str[0] != '\0') {
5085 compose_convert_header
5086 (compose, buf, sizeof(buf), str,
5087 strlen("Resent-To") + 2, TRUE);
5089 if (first_to_address) {
5090 err |= (fprintf(fp, "Resent-To: ") < 0);
5091 first_to_address = FALSE;
5093 err |= (fprintf(fp, ",") < 0);
5095 err |= (fprintf(fp, "%s", buf) < 0);
5099 if (!first_to_address) {
5100 err |= (fprintf(fp, "\n") < 0);
5103 first_cc_address = TRUE;
5104 for (list = compose->header_list; list; list = list->next) {
5105 headerentry = ((ComposeHeaderEntry *)list->data);
5106 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5108 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5109 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5110 Xstrdup_a(str, strg, return -1);
5112 if (str[0] != '\0') {
5113 compose_convert_header
5114 (compose, buf, sizeof(buf), str,
5115 strlen("Resent-Cc") + 2, TRUE);
5117 if (first_cc_address) {
5118 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5119 first_cc_address = FALSE;
5121 err |= (fprintf(fp, ",") < 0);
5123 err |= (fprintf(fp, "%s", buf) < 0);
5127 if (!first_cc_address) {
5128 err |= (fprintf(fp, "\n") < 0);
5131 return (err ? -1:0);
5134 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5136 gchar buf[BUFFSIZE];
5138 const gchar *entstr;
5139 /* struct utsname utsbuf; */
5140 gboolean err = FALSE;
5142 cm_return_val_if_fail(fp != NULL, -1);
5143 cm_return_val_if_fail(compose->account != NULL, -1);
5144 cm_return_val_if_fail(compose->account->address != NULL, -1);
5147 get_rfc822_date(buf, sizeof(buf));
5148 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5151 if (compose->account->name && *compose->account->name) {
5152 compose_convert_header
5153 (compose, buf, sizeof(buf), compose->account->name,
5154 strlen("From: "), TRUE);
5155 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5156 buf, compose->account->address) < 0);
5158 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5161 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5162 if (*entstr != '\0') {
5163 Xstrdup_a(str, entstr, return -1);
5166 compose_convert_header(compose, buf, sizeof(buf), str,
5167 strlen("Subject: "), FALSE);
5168 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5172 /* Resent-Message-ID */
5173 if (compose->account->set_domain && compose->account->domain) {
5174 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5175 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5176 g_snprintf(buf, sizeof(buf), "%s",
5177 strchr(compose->account->address, '@') ?
5178 strchr(compose->account->address, '@')+1 :
5179 compose->account->address);
5181 g_snprintf(buf, sizeof(buf), "%s", "");
5184 if (compose->account->gen_msgid) {
5186 if (compose->account->msgid_with_addr) {
5187 addr = compose->account->address;
5189 generate_msgid(buf, sizeof(buf), addr);
5190 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5191 compose->msgid = g_strdup(buf);
5193 compose->msgid = NULL;
5196 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5199 /* separator between header and body */
5200 err |= (fputs("\n", fp) == EOF);
5202 return (err ? -1:0);
5205 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5209 gchar buf[BUFFSIZE];
5211 gboolean skip = FALSE;
5212 gboolean err = FALSE;
5213 gchar *not_included[]={
5214 "Return-Path:", "Delivered-To:", "Received:",
5215 "Subject:", "X-UIDL:", "AF:",
5216 "NF:", "PS:", "SRH:",
5217 "SFN:", "DSR:", "MID:",
5218 "CFG:", "PT:", "S:",
5219 "RQ:", "SSV:", "NSV:",
5220 "SSH:", "R:", "MAID:",
5221 "NAID:", "RMID:", "FMID:",
5222 "SCF:", "RRCPT:", "NG:",
5223 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5224 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5225 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5226 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5227 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5230 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5231 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5235 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5237 for (i = 0; not_included[i] != NULL; i++) {
5238 if (g_ascii_strncasecmp(buf, not_included[i],
5239 strlen(not_included[i])) == 0) {
5246 if (fputs(buf, fdest) == -1)
5249 if (!prefs_common.redirect_keep_from) {
5250 if (g_ascii_strncasecmp(buf, "From:",
5251 strlen("From:")) == 0) {
5252 err |= (fputs(" (by way of ", fdest) == EOF);
5253 if (compose->account->name
5254 && *compose->account->name) {
5255 compose_convert_header
5256 (compose, buf, sizeof(buf),
5257 compose->account->name,
5260 err |= (fprintf(fdest, "%s <%s>",
5262 compose->account->address) < 0);
5264 err |= (fprintf(fdest, "%s",
5265 compose->account->address) < 0);
5266 err |= (fputs(")", fdest) == EOF);
5270 if (fputs("\n", fdest) == -1)
5277 if (compose_redirect_write_headers(compose, fdest))
5280 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5281 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5294 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5296 GtkTextBuffer *buffer;
5297 GtkTextIter start, end;
5300 const gchar *out_codeset;
5301 EncodingType encoding = ENC_UNKNOWN;
5302 MimeInfo *mimemsg, *mimetext;
5304 const gchar *src_codeset = CS_INTERNAL;
5305 gchar *from_addr = NULL;
5306 gchar *from_name = NULL;
5308 if (action == COMPOSE_WRITE_FOR_SEND)
5309 attach_parts = TRUE;
5311 /* create message MimeInfo */
5312 mimemsg = procmime_mimeinfo_new();
5313 mimemsg->type = MIMETYPE_MESSAGE;
5314 mimemsg->subtype = g_strdup("rfc822");
5315 mimemsg->content = MIMECONTENT_MEM;
5316 mimemsg->tmp = TRUE; /* must free content later */
5317 mimemsg->data.mem = compose_get_header(compose);
5319 /* Create text part MimeInfo */
5320 /* get all composed text */
5321 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5322 gtk_text_buffer_get_start_iter(buffer, &start);
5323 gtk_text_buffer_get_end_iter(buffer, &end);
5324 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5326 out_codeset = conv_get_charset_str(compose->out_encoding);
5328 if (!out_codeset && is_ascii_str(chars)) {
5329 out_codeset = CS_US_ASCII;
5330 } else if (prefs_common.outgoing_fallback_to_ascii &&
5331 is_ascii_str(chars)) {
5332 out_codeset = CS_US_ASCII;
5333 encoding = ENC_7BIT;
5337 gchar *test_conv_global_out = NULL;
5338 gchar *test_conv_reply = NULL;
5340 /* automatic mode. be automatic. */
5341 codeconv_set_strict(TRUE);
5343 out_codeset = conv_get_outgoing_charset_str();
5345 debug_print("trying to convert to %s\n", out_codeset);
5346 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5349 if (!test_conv_global_out && compose->orig_charset
5350 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5351 out_codeset = compose->orig_charset;
5352 debug_print("failure; trying to convert to %s\n", out_codeset);
5353 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5356 if (!test_conv_global_out && !test_conv_reply) {
5358 out_codeset = CS_INTERNAL;
5359 debug_print("failure; finally using %s\n", out_codeset);
5361 g_free(test_conv_global_out);
5362 g_free(test_conv_reply);
5363 codeconv_set_strict(FALSE);
5366 if (encoding == ENC_UNKNOWN) {
5367 if (prefs_common.encoding_method == CTE_BASE64)
5368 encoding = ENC_BASE64;
5369 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5370 encoding = ENC_QUOTED_PRINTABLE;
5371 else if (prefs_common.encoding_method == CTE_8BIT)
5372 encoding = ENC_8BIT;
5374 encoding = procmime_get_encoding_for_charset(out_codeset);
5377 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5378 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5380 if (action == COMPOSE_WRITE_FOR_SEND) {
5381 codeconv_set_strict(TRUE);
5382 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5383 codeconv_set_strict(FALSE);
5389 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5390 "to the specified %s charset.\n"
5391 "Send it as %s?"), out_codeset, src_codeset);
5392 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5393 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5396 if (aval != G_ALERTALTERNATE) {
5401 out_codeset = src_codeset;
5407 out_codeset = src_codeset;
5412 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5413 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5414 strstr(buf, "\nFrom ") != NULL) {
5415 encoding = ENC_QUOTED_PRINTABLE;
5419 mimetext = procmime_mimeinfo_new();
5420 mimetext->content = MIMECONTENT_MEM;
5421 mimetext->tmp = TRUE; /* must free content later */
5422 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5423 * and free the data, which we need later. */
5424 mimetext->data.mem = g_strdup(buf);
5425 mimetext->type = MIMETYPE_TEXT;
5426 mimetext->subtype = g_strdup("plain");
5427 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5428 g_strdup(out_codeset));
5430 /* protect trailing spaces when signing message */
5431 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5432 privacy_system_can_sign(compose->privacy_system)) {
5433 encoding = ENC_QUOTED_PRINTABLE;
5436 debug_print("main text: %zd bytes encoded as %s in %d\n",
5437 strlen(buf), out_codeset, encoding);
5439 /* check for line length limit */
5440 if (action == COMPOSE_WRITE_FOR_SEND &&
5441 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5442 check_line_length(buf, 1000, &line) < 0) {
5446 msg = g_strdup_printf
5447 (_("Line %d exceeds the line length limit (998 bytes).\n"
5448 "The contents of the message might be broken on the way to the delivery.\n"
5450 "Send it anyway?"), line + 1);
5451 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5453 if (aval != G_ALERTALTERNATE) {
5459 if (encoding != ENC_UNKNOWN)
5460 procmime_encode_content(mimetext, encoding);
5462 /* append attachment parts */
5463 if (compose_use_attach(compose) && attach_parts) {
5464 MimeInfo *mimempart;
5465 gchar *boundary = NULL;
5466 mimempart = procmime_mimeinfo_new();
5467 mimempart->content = MIMECONTENT_EMPTY;
5468 mimempart->type = MIMETYPE_MULTIPART;
5469 mimempart->subtype = g_strdup("mixed");
5473 boundary = generate_mime_boundary(NULL);
5474 } while (strstr(buf, boundary) != NULL);
5476 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5479 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5481 g_node_append(mimempart->node, mimetext->node);
5482 g_node_append(mimemsg->node, mimempart->node);
5484 if (compose_add_attachments(compose, mimempart) < 0)
5487 g_node_append(mimemsg->node, mimetext->node);
5491 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5492 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5493 /* extract name and address */
5494 if (strstr(spec, " <") && strstr(spec, ">")) {
5495 from_addr = g_strdup(strrchr(spec, '<')+1);
5496 *(strrchr(from_addr, '>')) = '\0';
5497 from_name = g_strdup(spec);
5498 *(strrchr(from_name, '<')) = '\0';
5505 /* sign message if sending */
5506 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5507 privacy_system_can_sign(compose->privacy_system))
5508 if (!privacy_sign(compose->privacy_system, mimemsg,
5509 compose->account, from_addr)) {
5516 procmime_write_mimeinfo(mimemsg, fp);
5518 procmime_mimeinfo_free_all(mimemsg);
5523 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5525 GtkTextBuffer *buffer;
5526 GtkTextIter start, end;
5531 if ((fp = g_fopen(file, "wb")) == NULL) {
5532 FILE_OP_ERROR(file, "fopen");
5536 /* chmod for security */
5537 if (change_file_mode_rw(fp, file) < 0) {
5538 FILE_OP_ERROR(file, "chmod");
5539 g_warning("can't change file mode\n");
5542 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5543 gtk_text_buffer_get_start_iter(buffer, &start);
5544 gtk_text_buffer_get_end_iter(buffer, &end);
5545 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5547 chars = conv_codeset_strdup
5548 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5551 if (!chars) return -1;
5554 len = strlen(chars);
5555 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5556 FILE_OP_ERROR(file, "fwrite");
5565 if (fclose(fp) == EOF) {
5566 FILE_OP_ERROR(file, "fclose");
5573 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5576 MsgInfo *msginfo = compose->targetinfo;
5578 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5579 if (!msginfo) return -1;
5581 if (!force && MSG_IS_LOCKED(msginfo->flags))
5584 item = msginfo->folder;
5585 cm_return_val_if_fail(item != NULL, -1);
5587 if (procmsg_msg_exist(msginfo) &&
5588 (folder_has_parent_of_type(item, F_QUEUE) ||
5589 folder_has_parent_of_type(item, F_DRAFT)
5590 || msginfo == compose->autosaved_draft)) {
5591 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5592 g_warning("can't remove the old message\n");
5595 debug_print("removed reedit target %d\n", msginfo->msgnum);
5602 static void compose_remove_draft(Compose *compose)
5605 MsgInfo *msginfo = compose->targetinfo;
5606 drafts = account_get_special_folder(compose->account, F_DRAFT);
5608 if (procmsg_msg_exist(msginfo)) {
5609 folder_item_remove_msg(drafts, msginfo->msgnum);
5614 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5615 gboolean remove_reedit_target)
5617 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5620 static gboolean compose_warn_encryption(Compose *compose)
5622 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5623 AlertValue val = G_ALERTALTERNATE;
5625 if (warning == NULL)
5628 val = alertpanel_full(_("Encryption warning"), warning,
5629 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5630 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5631 if (val & G_ALERTDISABLE) {
5632 val &= ~G_ALERTDISABLE;
5633 if (val == G_ALERTALTERNATE)
5634 privacy_inhibit_encrypt_warning(compose->privacy_system,
5638 if (val == G_ALERTALTERNATE) {
5645 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5646 gchar **msgpath, gboolean check_subject,
5647 gboolean remove_reedit_target)
5654 static gboolean lock = FALSE;
5655 PrefsAccount *mailac = NULL, *newsac = NULL;
5656 gboolean err = FALSE;
5658 debug_print("queueing message...\n");
5659 cm_return_val_if_fail(compose->account != NULL, -1);
5663 if (compose_check_entries(compose, check_subject) == FALSE) {
5665 if (compose->batch) {
5666 gtk_widget_show_all(compose->window);
5671 if (!compose->to_list && !compose->newsgroup_list) {
5672 g_warning("can't get recipient list.");
5677 if (compose->to_list) {
5678 if (compose->account->protocol != A_NNTP)
5679 mailac = compose->account;
5680 else if (cur_account && cur_account->protocol != A_NNTP)
5681 mailac = cur_account;
5682 else if (!(mailac = compose_current_mail_account())) {
5684 alertpanel_error(_("No account for sending mails available!"));
5689 if (compose->newsgroup_list) {
5690 if (compose->account->protocol == A_NNTP)
5691 newsac = compose->account;
5694 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5699 /* write queue header */
5700 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5701 G_DIR_SEPARATOR, compose, (guint) rand());
5702 debug_print("queuing to %s\n", tmp);
5703 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5704 FILE_OP_ERROR(tmp, "fopen");
5710 if (change_file_mode_rw(fp, tmp) < 0) {
5711 FILE_OP_ERROR(tmp, "chmod");
5712 g_warning("can't change file mode\n");
5715 /* queueing variables */
5716 err |= (fprintf(fp, "AF:\n") < 0);
5717 err |= (fprintf(fp, "NF:0\n") < 0);
5718 err |= (fprintf(fp, "PS:10\n") < 0);
5719 err |= (fprintf(fp, "SRH:1\n") < 0);
5720 err |= (fprintf(fp, "SFN:\n") < 0);
5721 err |= (fprintf(fp, "DSR:\n") < 0);
5723 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5725 err |= (fprintf(fp, "MID:\n") < 0);
5726 err |= (fprintf(fp, "CFG:\n") < 0);
5727 err |= (fprintf(fp, "PT:0\n") < 0);
5728 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5729 err |= (fprintf(fp, "RQ:\n") < 0);
5731 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5733 err |= (fprintf(fp, "SSV:\n") < 0);
5735 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5737 err |= (fprintf(fp, "NSV:\n") < 0);
5738 err |= (fprintf(fp, "SSH:\n") < 0);
5739 /* write recepient list */
5740 if (compose->to_list) {
5741 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5742 for (cur = compose->to_list->next; cur != NULL;
5744 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5745 err |= (fprintf(fp, "\n") < 0);
5747 /* write newsgroup list */
5748 if (compose->newsgroup_list) {
5749 err |= (fprintf(fp, "NG:") < 0);
5750 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5751 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5752 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5753 err |= (fprintf(fp, "\n") < 0);
5755 /* Sylpheed account IDs */
5757 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5759 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5762 if (compose->privacy_system != NULL) {
5763 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5764 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5765 if (compose->use_encryption) {
5767 if (!compose_warn_encryption(compose)) {
5774 if (mailac && mailac->encrypt_to_self) {
5775 GSList *tmp_list = g_slist_copy(compose->to_list);
5776 tmp_list = g_slist_append(tmp_list, compose->account->address);
5777 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5778 g_slist_free(tmp_list);
5780 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5782 if (encdata != NULL) {
5783 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5784 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5785 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5787 } /* else we finally dont want to encrypt */
5789 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5790 /* and if encdata was null, it means there's been a problem in
5802 /* Save copy folder */
5803 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5804 gchar *savefolderid;
5806 savefolderid = compose_get_save_to(compose);
5807 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5808 g_free(savefolderid);
5810 /* Save copy folder */
5811 if (compose->return_receipt) {
5812 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5814 /* Message-ID of message replying to */
5815 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5818 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5819 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5822 /* Message-ID of message forwarding to */
5823 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5826 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5827 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5831 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5832 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5834 /* end of headers */
5835 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5837 if (compose->redirect_filename != NULL) {
5838 if (compose_redirect_write_to_file(compose, fp) < 0) {
5847 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5852 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5856 g_warning("failed to write queue message\n");
5863 if (fclose(fp) == EOF) {
5864 FILE_OP_ERROR(tmp, "fclose");
5871 if (item && *item) {
5874 queue = account_get_special_folder(compose->account, F_QUEUE);
5877 g_warning("can't find queue folder\n");
5883 folder_item_scan(queue);
5884 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5885 g_warning("can't queue the message\n");
5892 if (msgpath == NULL) {
5898 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5899 compose_remove_reedit_target(compose, FALSE);
5902 if ((msgnum != NULL) && (item != NULL)) {
5910 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5913 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5915 struct stat statbuf;
5916 gchar *type, *subtype;
5917 GtkTreeModel *model;
5920 model = gtk_tree_view_get_model(tree_view);
5922 if (!gtk_tree_model_get_iter_first(model, &iter))
5925 gtk_tree_model_get(model, &iter,
5929 if (!is_file_exist(ainfo->file)) {
5930 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5931 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5932 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5934 if (val == G_ALERTDEFAULT) {
5939 mimepart = procmime_mimeinfo_new();
5940 mimepart->content = MIMECONTENT_FILE;
5941 mimepart->data.filename = g_strdup(ainfo->file);
5942 mimepart->tmp = FALSE; /* or we destroy our attachment */
5943 mimepart->offset = 0;
5945 g_stat(ainfo->file, &statbuf);
5946 mimepart->length = statbuf.st_size;
5948 type = g_strdup(ainfo->content_type);
5950 if (!strchr(type, '/')) {
5952 type = g_strdup("application/octet-stream");
5955 subtype = strchr(type, '/') + 1;
5956 *(subtype - 1) = '\0';
5957 mimepart->type = procmime_get_media_type(type);
5958 mimepart->subtype = g_strdup(subtype);
5961 if (mimepart->type == MIMETYPE_MESSAGE &&
5962 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5963 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5966 if (mimepart->type == MIMETYPE_APPLICATION &&
5967 !strcmp2(mimepart->subtype, "octet-stream"))
5968 g_hash_table_insert(mimepart->typeparameters,
5969 g_strdup("name"), g_strdup(ainfo->name));
5970 g_hash_table_insert(mimepart->dispositionparameters,
5971 g_strdup("filename"), g_strdup(ainfo->name));
5972 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5976 if (compose->use_signing) {
5977 if (ainfo->encoding == ENC_7BIT)
5978 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5979 else if (ainfo->encoding == ENC_8BIT)
5980 ainfo->encoding = ENC_BASE64;
5983 procmime_encode_content(mimepart, ainfo->encoding);
5985 g_node_append(parent->node, mimepart->node);
5986 } while (gtk_tree_model_iter_next(model, &iter));
5991 #define IS_IN_CUSTOM_HEADER(header) \
5992 (compose->account->add_customhdr && \
5993 custom_header_find(compose->account->customhdr_list, header) != NULL)
5995 static void compose_add_headerfield_from_headerlist(Compose *compose,
5997 const gchar *fieldname,
5998 const gchar *seperator)
6000 gchar *str, *fieldname_w_colon;
6001 gboolean add_field = FALSE;
6003 ComposeHeaderEntry *headerentry;
6004 const gchar *headerentryname;
6005 const gchar *trans_fieldname;
6008 if (IS_IN_CUSTOM_HEADER(fieldname))
6011 debug_print("Adding %s-fields\n", fieldname);
6013 fieldstr = g_string_sized_new(64);
6015 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6016 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6018 for (list = compose->header_list; list; list = list->next) {
6019 headerentry = ((ComposeHeaderEntry *)list->data);
6020 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6022 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6023 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6025 if (str[0] != '\0') {
6027 g_string_append(fieldstr, seperator);
6028 g_string_append(fieldstr, str);
6037 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6038 compose_convert_header
6039 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6040 strlen(fieldname) + 2, TRUE);
6041 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6045 g_free(fieldname_w_colon);
6046 g_string_free(fieldstr, TRUE);
6051 static gchar *compose_get_header(Compose *compose)
6053 gchar buf[BUFFSIZE];
6054 const gchar *entry_str;
6058 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6060 gchar *from_name = NULL, *from_address = NULL;
6063 cm_return_val_if_fail(compose->account != NULL, NULL);
6064 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6066 header = g_string_sized_new(64);
6069 get_rfc822_date(buf, sizeof(buf));
6070 g_string_append_printf(header, "Date: %s\n", buf);
6074 if (compose->account->name && *compose->account->name) {
6076 QUOTE_IF_REQUIRED(buf, compose->account->name);
6077 tmp = g_strdup_printf("%s <%s>",
6078 buf, compose->account->address);
6080 tmp = g_strdup_printf("%s",
6081 compose->account->address);
6083 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6084 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6086 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6087 from_address = g_strdup(compose->account->address);
6089 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6090 /* extract name and address */
6091 if (strstr(spec, " <") && strstr(spec, ">")) {
6092 from_address = g_strdup(strrchr(spec, '<')+1);
6093 *(strrchr(from_address, '>')) = '\0';
6094 from_name = g_strdup(spec);
6095 *(strrchr(from_name, '<')) = '\0';
6098 from_address = g_strdup(spec);
6105 if (from_name && *from_name) {
6106 compose_convert_header
6107 (compose, buf, sizeof(buf), from_name,
6108 strlen("From: "), TRUE);
6109 QUOTE_IF_REQUIRED(name, buf);
6111 g_string_append_printf(header, "From: %s <%s>\n",
6112 name, from_address);
6114 g_string_append_printf(header, "From: %s\n", from_address);
6117 g_free(from_address);
6120 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6123 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6126 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6130 * If this account is a NNTP account remove Bcc header from
6131 * message body since it otherwise will be publicly shown
6133 if (compose->account->protocol != A_NNTP)
6134 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6137 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6139 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6142 compose_convert_header(compose, buf, sizeof(buf), str,
6143 strlen("Subject: "), FALSE);
6144 g_string_append_printf(header, "Subject: %s\n", buf);
6150 if (compose->account->set_domain && compose->account->domain) {
6151 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6152 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6153 g_snprintf(buf, sizeof(buf), "%s",
6154 strchr(compose->account->address, '@') ?
6155 strchr(compose->account->address, '@')+1 :
6156 compose->account->address);
6158 g_snprintf(buf, sizeof(buf), "%s", "");
6161 if (compose->account->gen_msgid) {
6163 if (compose->account->msgid_with_addr) {
6164 addr = compose->account->address;
6166 generate_msgid(buf, sizeof(buf), addr);
6167 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6168 compose->msgid = g_strdup(buf);
6170 compose->msgid = NULL;
6173 if (compose->remove_references == FALSE) {
6175 if (compose->inreplyto && compose->to_list)
6176 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6179 if (compose->references)
6180 g_string_append_printf(header, "References: %s\n", compose->references);
6184 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6187 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6190 if (compose->account->organization &&
6191 strlen(compose->account->organization) &&
6192 !IS_IN_CUSTOM_HEADER("Organization")) {
6193 compose_convert_header(compose, buf, sizeof(buf),
6194 compose->account->organization,
6195 strlen("Organization: "), FALSE);
6196 g_string_append_printf(header, "Organization: %s\n", buf);
6199 /* Program version and system info */
6200 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6201 !compose->newsgroup_list) {
6202 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6204 gtk_major_version, gtk_minor_version, gtk_micro_version,
6207 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6208 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6210 gtk_major_version, gtk_minor_version, gtk_micro_version,
6214 /* custom headers */
6215 if (compose->account->add_customhdr) {
6218 for (cur = compose->account->customhdr_list; cur != NULL;
6220 CustomHeader *chdr = (CustomHeader *)cur->data;
6222 if (custom_header_is_allowed(chdr->name)) {
6223 compose_convert_header
6224 (compose, buf, sizeof(buf),
6225 chdr->value ? chdr->value : "",
6226 strlen(chdr->name) + 2, FALSE);
6227 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6232 /* Automatic Faces and X-Faces */
6233 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6234 g_string_append_printf(header, "X-Face: %s\n", buf);
6236 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6237 g_string_append_printf(header, "X-Face: %s\n", buf);
6239 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6240 g_string_append_printf(header, "Face: %s\n", buf);
6242 else if (get_default_face (buf, sizeof(buf)) == 0) {
6243 g_string_append_printf(header, "Face: %s\n", buf);
6247 switch (compose->priority) {
6248 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6249 "X-Priority: 1 (Highest)\n");
6251 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6252 "X-Priority: 2 (High)\n");
6254 case PRIORITY_NORMAL: break;
6255 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6256 "X-Priority: 4 (Low)\n");
6258 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6259 "X-Priority: 5 (Lowest)\n");
6261 default: debug_print("compose: priority unknown : %d\n",
6265 /* Request Return Receipt */
6266 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6267 if (compose->return_receipt) {
6268 if (compose->account->name
6269 && *compose->account->name) {
6270 compose_convert_header(compose, buf, sizeof(buf),
6271 compose->account->name,
6272 strlen("Disposition-Notification-To: "),
6274 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6276 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6280 /* get special headers */
6281 for (list = compose->header_list; list; list = list->next) {
6282 ComposeHeaderEntry *headerentry;
6285 gchar *headername_wcolon;
6286 const gchar *headername_trans;
6289 gboolean standard_header = FALSE;
6291 headerentry = ((ComposeHeaderEntry *)list->data);
6293 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6295 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6300 if (!strstr(tmp, ":")) {
6301 headername_wcolon = g_strconcat(tmp, ":", NULL);
6302 headername = g_strdup(tmp);
6304 headername_wcolon = g_strdup(tmp);
6305 headername = g_strdup(strtok(tmp, ":"));
6309 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6310 Xstrdup_a(headervalue, entry_str, return NULL);
6311 subst_char(headervalue, '\r', ' ');
6312 subst_char(headervalue, '\n', ' ');
6313 string = std_headers;
6314 while (*string != NULL) {
6315 headername_trans = prefs_common_translated_header_name(*string);
6316 if (!strcmp(headername_trans, headername_wcolon))
6317 standard_header = TRUE;
6320 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6321 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6324 g_free(headername_wcolon);
6328 g_string_free(header, FALSE);
6333 #undef IS_IN_CUSTOM_HEADER
6335 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6336 gint header_len, gboolean addr_field)
6338 gchar *tmpstr = NULL;
6339 const gchar *out_codeset = NULL;
6341 cm_return_if_fail(src != NULL);
6342 cm_return_if_fail(dest != NULL);
6344 if (len < 1) return;
6346 tmpstr = g_strdup(src);
6348 subst_char(tmpstr, '\n', ' ');
6349 subst_char(tmpstr, '\r', ' ');
6352 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6353 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6354 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6359 codeconv_set_strict(TRUE);
6360 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6361 conv_get_charset_str(compose->out_encoding));
6362 codeconv_set_strict(FALSE);
6364 if (!dest || *dest == '\0') {
6365 gchar *test_conv_global_out = NULL;
6366 gchar *test_conv_reply = NULL;
6368 /* automatic mode. be automatic. */
6369 codeconv_set_strict(TRUE);
6371 out_codeset = conv_get_outgoing_charset_str();
6373 debug_print("trying to convert to %s\n", out_codeset);
6374 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6377 if (!test_conv_global_out && compose->orig_charset
6378 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6379 out_codeset = compose->orig_charset;
6380 debug_print("failure; trying to convert to %s\n", out_codeset);
6381 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6384 if (!test_conv_global_out && !test_conv_reply) {
6386 out_codeset = CS_INTERNAL;
6387 debug_print("finally using %s\n", out_codeset);
6389 g_free(test_conv_global_out);
6390 g_free(test_conv_reply);
6391 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6393 codeconv_set_strict(FALSE);
6398 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6402 cm_return_if_fail(user_data != NULL);
6404 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6405 g_strstrip(address);
6406 if (*address != '\0') {
6407 gchar *name = procheader_get_fromname(address);
6408 extract_address(address);
6409 addressbook_add_contact(name, address, NULL, NULL);
6414 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6416 GtkWidget *menuitem;
6419 cm_return_if_fail(menu != NULL);
6420 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6422 menuitem = gtk_separator_menu_item_new();
6423 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6424 gtk_widget_show(menuitem);
6426 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6427 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6429 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6430 g_strstrip(address);
6431 if (*address == '\0') {
6432 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6435 g_signal_connect(G_OBJECT(menuitem), "activate",
6436 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6437 gtk_widget_show(menuitem);
6440 static void compose_create_header_entry(Compose *compose)
6442 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6449 const gchar *header = NULL;
6450 ComposeHeaderEntry *headerentry;
6451 gboolean standard_header = FALSE;
6452 GtkListStore *model;
6454 #if !(GTK_CHECK_VERSION(2,12,0))
6455 GtkTooltips *tips = compose->tooltips;
6458 headerentry = g_new0(ComposeHeaderEntry, 1);
6461 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6462 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6463 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6465 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6467 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6469 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6470 COMPOSE_NEWSGROUPS);
6471 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6473 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6474 COMPOSE_FOLLOWUPTO);
6476 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6477 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6478 G_CALLBACK(compose_grab_focus_cb), compose);
6479 gtk_widget_show(combo);
6480 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6481 compose->header_nextrow, compose->header_nextrow+1,
6482 GTK_SHRINK, GTK_FILL, 0, 0);
6483 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6484 const gchar *last_header_entry = gtk_entry_get_text(
6485 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6487 while (*string != NULL) {
6488 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6489 standard_header = TRUE;
6492 if (standard_header)
6493 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6495 if (!compose->header_last || !standard_header) {
6496 switch(compose->account->protocol) {
6498 header = prefs_common_translated_header_name("Newsgroups:");
6501 header = prefs_common_translated_header_name("To:");
6506 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6508 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6509 G_CALLBACK(compose_grab_focus_cb), compose);
6511 /* Entry field with cleanup button */
6512 button = gtk_button_new();
6513 gtk_button_set_image(GTK_BUTTON(button),
6514 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6515 gtk_widget_show(button);
6516 CLAWS_SET_TIP(button,
6517 _("Delete entry contents"));
6518 entry = gtk_entry_new();
6519 gtk_widget_show(entry);
6520 CLAWS_SET_TIP(entry,
6521 _("Use <tab> to autocomplete from addressbook"));
6522 hbox = gtk_hbox_new (FALSE, 0);
6523 gtk_widget_show(hbox);
6524 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6525 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6526 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6527 compose->header_nextrow, compose->header_nextrow+1,
6528 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6530 g_signal_connect(G_OBJECT(entry), "key-press-event",
6531 G_CALLBACK(compose_headerentry_key_press_event_cb),
6533 g_signal_connect(G_OBJECT(entry), "changed",
6534 G_CALLBACK(compose_headerentry_changed_cb),
6536 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6537 G_CALLBACK(compose_grab_focus_cb), compose);
6539 g_signal_connect(G_OBJECT(button), "clicked",
6540 G_CALLBACK(compose_headerentry_button_clicked_cb),
6544 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6545 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6546 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6547 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6548 G_CALLBACK(compose_header_drag_received_cb),
6550 g_signal_connect(G_OBJECT(entry), "drag-drop",
6551 G_CALLBACK(compose_drag_drop),
6553 g_signal_connect(G_OBJECT(entry), "populate-popup",
6554 G_CALLBACK(compose_entry_popup_extend),
6557 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6559 headerentry->compose = compose;
6560 headerentry->combo = combo;
6561 headerentry->entry = entry;
6562 headerentry->button = button;
6563 headerentry->hbox = hbox;
6564 headerentry->headernum = compose->header_nextrow;
6565 headerentry->type = PREF_NONE;
6567 compose->header_nextrow++;
6568 compose->header_last = headerentry;
6569 compose->header_list =
6570 g_slist_append(compose->header_list,
6574 static void compose_add_header_entry(Compose *compose, const gchar *header,
6575 gchar *text, ComposePrefType pref_type)
6577 ComposeHeaderEntry *last_header = compose->header_last;
6578 gchar *tmp = g_strdup(text), *email;
6579 gboolean replyto_hdr;
6581 replyto_hdr = (!strcasecmp(header,
6582 prefs_common_translated_header_name("Reply-To:")) ||
6584 prefs_common_translated_header_name("Followup-To:")) ||
6586 prefs_common_translated_header_name("In-Reply-To:")));
6588 extract_address(tmp);
6589 email = g_utf8_strdown(tmp, -1);
6591 if (replyto_hdr == FALSE &&
6592 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6594 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6595 header, text, (gint) pref_type);
6601 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6602 gtk_entry_set_text(GTK_ENTRY(
6603 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6605 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6606 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6607 last_header->type = pref_type;
6609 if (replyto_hdr == FALSE)
6610 g_hash_table_insert(compose->email_hashtable, email,
6611 GUINT_TO_POINTER(1));
6618 static void compose_destroy_headerentry(Compose *compose,
6619 ComposeHeaderEntry *headerentry)
6621 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6624 extract_address(text);
6625 email = g_utf8_strdown(text, -1);
6626 g_hash_table_remove(compose->email_hashtable, email);
6630 gtk_widget_destroy(headerentry->combo);
6631 gtk_widget_destroy(headerentry->entry);
6632 gtk_widget_destroy(headerentry->button);
6633 gtk_widget_destroy(headerentry->hbox);
6634 g_free(headerentry);
6637 static void compose_remove_header_entries(Compose *compose)
6640 for (list = compose->header_list; list; list = list->next)
6641 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6643 compose->header_last = NULL;
6644 g_slist_free(compose->header_list);
6645 compose->header_list = NULL;
6646 compose->header_nextrow = 1;
6647 compose_create_header_entry(compose);
6650 static GtkWidget *compose_create_header(Compose *compose)
6652 GtkWidget *from_optmenu_hbox;
6653 GtkWidget *header_scrolledwin;
6654 GtkWidget *header_table;
6658 /* header labels and entries */
6659 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6660 gtk_widget_show(header_scrolledwin);
6661 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6663 header_table = gtk_table_new(2, 2, FALSE);
6664 gtk_widget_show(header_table);
6665 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6666 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6667 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6670 /* option menu for selecting accounts */
6671 from_optmenu_hbox = compose_account_option_menu_create(compose);
6672 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6673 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6676 compose->header_table = header_table;
6677 compose->header_list = NULL;
6678 compose->header_nextrow = count;
6680 compose_create_header_entry(compose);
6682 compose->table = NULL;
6684 return header_scrolledwin ;
6687 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6689 Compose *compose = (Compose *)data;
6690 GdkEventButton event;
6693 event.time = gtk_get_current_event_time();
6695 return attach_button_pressed(compose->attach_clist, &event, compose);
6698 static GtkWidget *compose_create_attach(Compose *compose)
6700 GtkWidget *attach_scrwin;
6701 GtkWidget *attach_clist;
6703 GtkListStore *store;
6704 GtkCellRenderer *renderer;
6705 GtkTreeViewColumn *column;
6706 GtkTreeSelection *selection;
6708 /* attachment list */
6709 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6710 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6711 GTK_POLICY_AUTOMATIC,
6712 GTK_POLICY_AUTOMATIC);
6713 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6715 store = gtk_list_store_new(N_ATTACH_COLS,
6720 G_TYPE_AUTO_POINTER,
6722 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6723 (GTK_TREE_MODEL(store)));
6724 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6725 g_object_unref(store);
6727 renderer = gtk_cell_renderer_text_new();
6728 column = gtk_tree_view_column_new_with_attributes
6729 (_("Mime type"), renderer, "text",
6730 COL_MIMETYPE, NULL);
6731 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6733 renderer = gtk_cell_renderer_text_new();
6734 column = gtk_tree_view_column_new_with_attributes
6735 (_("Size"), renderer, "text",
6737 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6739 renderer = gtk_cell_renderer_text_new();
6740 column = gtk_tree_view_column_new_with_attributes
6741 (_("Name"), renderer, "text",
6743 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6745 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6746 prefs_common.use_stripes_everywhere);
6747 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6748 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6750 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6751 G_CALLBACK(attach_selected), compose);
6752 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6753 G_CALLBACK(attach_button_pressed), compose);
6755 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6756 G_CALLBACK(popup_attach_button_pressed), compose);
6758 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6759 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6760 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6761 G_CALLBACK(popup_attach_button_pressed), compose);
6763 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6764 G_CALLBACK(attach_key_pressed), compose);
6767 gtk_drag_dest_set(attach_clist,
6768 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6769 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6770 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6771 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6772 G_CALLBACK(compose_attach_drag_received_cb),
6774 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6775 G_CALLBACK(compose_drag_drop),
6778 compose->attach_scrwin = attach_scrwin;
6779 compose->attach_clist = attach_clist;
6781 return attach_scrwin;
6784 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6785 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6787 static GtkWidget *compose_create_others(Compose *compose)
6790 GtkWidget *savemsg_checkbtn;
6791 GtkWidget *savemsg_combo;
6792 GtkWidget *savemsg_select;
6795 gchar *folderidentifier;
6797 /* Table for settings */
6798 table = gtk_table_new(3, 1, FALSE);
6799 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6800 gtk_widget_show(table);
6801 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6804 /* Save Message to folder */
6805 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6806 gtk_widget_show(savemsg_checkbtn);
6807 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6808 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6809 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6811 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6812 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6814 savemsg_combo = gtk_combo_box_entry_new_text();
6815 compose->savemsg_checkbtn = savemsg_checkbtn;
6816 compose->savemsg_combo = savemsg_combo;
6817 gtk_widget_show(savemsg_combo);
6819 if (prefs_common.compose_save_to_history)
6820 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6821 prefs_common.compose_save_to_history);
6823 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6824 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6825 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6826 G_CALLBACK(compose_grab_focus_cb), compose);
6827 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6828 folderidentifier = folder_item_get_identifier(account_get_special_folder
6829 (compose->account, F_OUTBOX));
6830 compose_set_save_to(compose, folderidentifier);
6831 g_free(folderidentifier);
6834 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6835 gtk_widget_show(savemsg_select);
6836 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6837 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6838 G_CALLBACK(compose_savemsg_select_cb),
6846 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6848 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6849 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6852 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6857 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6860 path = folder_item_get_identifier(dest);
6862 compose_set_save_to(compose, path);
6866 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6867 GdkAtom clip, GtkTextIter *insert_place);
6870 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6874 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6876 if (event->button == 3) {
6878 GtkTextIter sel_start, sel_end;
6879 gboolean stuff_selected;
6881 /* move the cursor to allow GtkAspell to check the word
6882 * under the mouse */
6883 if (event->x && event->y) {
6884 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6885 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6887 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6890 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6891 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6894 stuff_selected = gtk_text_buffer_get_selection_bounds(
6896 &sel_start, &sel_end);
6898 gtk_text_buffer_place_cursor (buffer, &iter);
6899 /* reselect stuff */
6901 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6902 gtk_text_buffer_select_range(buffer,
6903 &sel_start, &sel_end);
6905 return FALSE; /* pass the event so that the right-click goes through */
6908 if (event->button == 2) {
6913 /* get the middle-click position to paste at the correct place */
6914 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6915 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6917 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6920 entry_paste_clipboard(compose, text,
6921 prefs_common.linewrap_pastes,
6922 GDK_SELECTION_PRIMARY, &iter);
6930 static void compose_spell_menu_changed(void *data)
6932 Compose *compose = (Compose *)data;
6934 GtkWidget *menuitem;
6935 GtkWidget *parent_item;
6936 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6939 if (compose->gtkaspell == NULL)
6942 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6943 "/Menu/Spelling/Options");
6945 /* setting the submenu removes /Spelling/Options from the factory
6946 * so we need to save it */
6948 if (parent_item == NULL) {
6949 parent_item = compose->aspell_options_menu;
6950 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6952 compose->aspell_options_menu = parent_item;
6954 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6956 spell_menu = g_slist_reverse(spell_menu);
6957 for (items = spell_menu;
6958 items; items = items->next) {
6959 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6960 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6961 gtk_widget_show(GTK_WIDGET(menuitem));
6963 g_slist_free(spell_menu);
6965 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6966 gtk_widget_show(parent_item);
6969 static void compose_dict_changed(void *data)
6971 Compose *compose = (Compose *) data;
6973 if(compose->gtkaspell &&
6974 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6977 gtkaspell_highlight_all(compose->gtkaspell);
6978 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6982 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6984 Compose *compose = (Compose *)data;
6985 GdkEventButton event;
6988 event.time = gtk_get_current_event_time();
6992 return text_clicked(compose->text, &event, compose);
6995 static gboolean compose_force_window_origin = TRUE;
6996 static Compose *compose_create(PrefsAccount *account,
7005 GtkWidget *handlebox;
7007 GtkWidget *notebook;
7009 GtkWidget *attach_hbox;
7010 GtkWidget *attach_lab1;
7011 GtkWidget *attach_lab2;
7016 GtkWidget *subject_hbox;
7017 GtkWidget *subject_frame;
7018 GtkWidget *subject_entry;
7022 GtkWidget *edit_vbox;
7023 GtkWidget *ruler_hbox;
7025 GtkWidget *scrolledwin;
7027 GtkTextBuffer *buffer;
7028 GtkClipboard *clipboard;
7031 UndoMain *undostruct;
7033 gchar *titles[N_ATTACH_COLS];
7034 GtkWidget *popupmenu;
7035 GtkWidget *tmpl_menu;
7036 GtkActionGroup *action_group = NULL;
7039 GtkAspell * gtkaspell = NULL;
7042 static GdkGeometry geometry;
7044 cm_return_val_if_fail(account != NULL, NULL);
7046 debug_print("Creating compose window...\n");
7047 compose = g_new0(Compose, 1);
7049 titles[COL_MIMETYPE] = _("MIME type");
7050 titles[COL_SIZE] = _("Size");
7051 titles[COL_NAME] = _("Name");
7053 compose->batch = batch;
7054 compose->account = account;
7055 compose->folder = folder;
7057 compose->mutex = g_mutex_new();
7058 compose->set_cursor_pos = -1;
7060 #if !(GTK_CHECK_VERSION(2,12,0))
7061 compose->tooltips = tips;
7064 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7066 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7067 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7069 if (!geometry.max_width) {
7070 geometry.max_width = gdk_screen_width();
7071 geometry.max_height = gdk_screen_height();
7074 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7075 &geometry, GDK_HINT_MAX_SIZE);
7076 if (!geometry.min_width) {
7077 geometry.min_width = 600;
7078 geometry.min_height = 440;
7080 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7081 &geometry, GDK_HINT_MIN_SIZE);
7083 #ifndef GENERIC_UMPC
7084 if (compose_force_window_origin)
7085 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7086 prefs_common.compose_y);
7088 g_signal_connect(G_OBJECT(window), "delete_event",
7089 G_CALLBACK(compose_delete_cb), compose);
7090 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7091 gtk_widget_realize(window);
7093 gtkut_widget_set_composer_icon(window);
7095 vbox = gtk_vbox_new(FALSE, 0);
7096 gtk_container_add(GTK_CONTAINER(window), vbox);
7098 compose->ui_manager = gtk_ui_manager_new();
7099 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7100 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7101 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7102 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7103 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7104 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7105 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7106 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7107 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7108 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7111 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7116 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7133 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7159 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7217 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7219 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7227 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7228 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)
7229 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)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7235 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)
7236 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)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7240 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7241 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)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7245 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)
7246 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7248 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7251 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)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7257 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)
7258 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)
7259 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7260 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7263 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7264 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7267 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7269 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7270 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7271 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)
7273 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7279 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7280 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7281 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7282 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7283 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7284 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7287 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7289 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7290 gtk_widget_show_all(menubar);
7292 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7294 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7296 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7299 if (prefs_common.toolbar_detachable) {
7300 handlebox = gtk_handle_box_new();
7302 handlebox = gtk_hbox_new(FALSE, 0);
7304 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7306 gtk_widget_realize(handlebox);
7308 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7311 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7315 vbox2 = gtk_vbox_new(FALSE, 2);
7316 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7317 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7320 notebook = gtk_notebook_new();
7321 gtk_widget_set_size_request(notebook, -1, 130);
7322 gtk_widget_show(notebook);
7324 /* header labels and entries */
7325 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7326 compose_create_header(compose),
7327 gtk_label_new_with_mnemonic(_("Hea_der")));
7328 /* attachment list */
7329 attach_hbox = gtk_hbox_new(FALSE, 0);
7330 gtk_widget_show(attach_hbox);
7332 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7333 gtk_widget_show(attach_lab1);
7334 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7336 attach_lab2 = gtk_label_new("");
7337 gtk_widget_show(attach_lab2);
7338 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7340 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7341 compose_create_attach(compose),
7344 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7345 compose_create_others(compose),
7346 gtk_label_new_with_mnemonic(_("Othe_rs")));
7349 subject_hbox = gtk_hbox_new(FALSE, 0);
7350 gtk_widget_show(subject_hbox);
7352 subject_frame = gtk_frame_new(NULL);
7353 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7354 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7355 gtk_widget_show(subject_frame);
7357 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7358 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7359 gtk_widget_show(subject);
7361 label = gtk_label_new(_("Subject:"));
7362 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7363 gtk_widget_show(label);
7366 subject_entry = claws_spell_entry_new();
7368 subject_entry = gtk_entry_new();
7370 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7371 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7372 G_CALLBACK(compose_grab_focus_cb), compose);
7373 gtk_widget_show(subject_entry);
7374 compose->subject_entry = subject_entry;
7375 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7377 edit_vbox = gtk_vbox_new(FALSE, 0);
7379 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7382 ruler_hbox = gtk_hbox_new(FALSE, 0);
7383 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7385 ruler = gtk_shruler_new();
7386 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7387 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7391 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7392 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7393 GTK_POLICY_AUTOMATIC,
7394 GTK_POLICY_AUTOMATIC);
7395 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7397 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7398 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7400 text = gtk_text_view_new();
7401 if (prefs_common.show_compose_margin) {
7402 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7403 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7405 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7406 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7407 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7408 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7409 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7411 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7413 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7414 G_CALLBACK(compose_edit_size_alloc),
7416 g_signal_connect(G_OBJECT(buffer), "changed",
7417 G_CALLBACK(compose_changed_cb), compose);
7418 g_signal_connect(G_OBJECT(text), "grab_focus",
7419 G_CALLBACK(compose_grab_focus_cb), compose);
7420 g_signal_connect(G_OBJECT(buffer), "insert_text",
7421 G_CALLBACK(text_inserted), compose);
7422 g_signal_connect(G_OBJECT(text), "button_press_event",
7423 G_CALLBACK(text_clicked), compose);
7425 g_signal_connect(G_OBJECT(text), "popup-menu",
7426 G_CALLBACK(compose_popup_menu), compose);
7428 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7429 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7430 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7431 G_CALLBACK(compose_popup_menu), compose);
7433 g_signal_connect(G_OBJECT(subject_entry), "changed",
7434 G_CALLBACK(compose_changed_cb), compose);
7437 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7438 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7439 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7440 g_signal_connect(G_OBJECT(text), "drag_data_received",
7441 G_CALLBACK(compose_insert_drag_received_cb),
7443 g_signal_connect(G_OBJECT(text), "drag-drop",
7444 G_CALLBACK(compose_drag_drop),
7446 gtk_widget_show_all(vbox);
7448 /* pane between attach clist and text */
7449 paned = gtk_vpaned_new();
7450 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7452 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7453 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7455 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7457 gtk_paned_add1(GTK_PANED(paned), notebook);
7458 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7459 gtk_widget_show_all(paned);
7462 if (prefs_common.textfont) {
7463 PangoFontDescription *font_desc;
7465 font_desc = pango_font_description_from_string
7466 (prefs_common.textfont);
7468 gtk_widget_modify_font(text, font_desc);
7469 pango_font_description_free(font_desc);
7473 gtk_action_group_add_actions(action_group, compose_popup_entries,
7474 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7476 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7477 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7478 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7480 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7482 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7484 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7485 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7486 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7488 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7490 undostruct = undo_init(text);
7491 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7494 address_completion_start(window);
7496 compose->window = window;
7497 compose->vbox = vbox;
7498 compose->menubar = menubar;
7499 compose->handlebox = handlebox;
7501 compose->vbox2 = vbox2;
7503 compose->paned = paned;
7505 compose->attach_label = attach_lab2;
7507 compose->notebook = notebook;
7508 compose->edit_vbox = edit_vbox;
7509 compose->ruler_hbox = ruler_hbox;
7510 compose->ruler = ruler;
7511 compose->scrolledwin = scrolledwin;
7512 compose->text = text;
7514 compose->focused_editable = NULL;
7516 compose->popupmenu = popupmenu;
7518 compose->tmpl_menu = tmpl_menu;
7520 compose->mode = mode;
7521 compose->rmode = mode;
7523 compose->targetinfo = NULL;
7524 compose->replyinfo = NULL;
7525 compose->fwdinfo = NULL;
7527 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7528 g_str_equal, (GDestroyNotify) g_free, NULL);
7530 compose->replyto = NULL;
7532 compose->bcc = NULL;
7533 compose->followup_to = NULL;
7535 compose->ml_post = NULL;
7537 compose->inreplyto = NULL;
7538 compose->references = NULL;
7539 compose->msgid = NULL;
7540 compose->boundary = NULL;
7542 compose->autowrap = prefs_common.autowrap;
7543 compose->autoindent = prefs_common.auto_indent;
7544 compose->use_signing = FALSE;
7545 compose->use_encryption = FALSE;
7546 compose->privacy_system = NULL;
7548 compose->modified = FALSE;
7550 compose->return_receipt = FALSE;
7552 compose->to_list = NULL;
7553 compose->newsgroup_list = NULL;
7555 compose->undostruct = undostruct;
7557 compose->sig_str = NULL;
7559 compose->exteditor_file = NULL;
7560 compose->exteditor_pid = -1;
7561 compose->exteditor_tag = -1;
7562 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7565 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7566 if (mode != COMPOSE_REDIRECT) {
7567 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7568 strcmp(prefs_common.dictionary, "")) {
7569 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7570 prefs_common.alt_dictionary,
7571 conv_get_locale_charset_str(),
7572 prefs_common.misspelled_col,
7573 prefs_common.check_while_typing,
7574 prefs_common.recheck_when_changing_dict,
7575 prefs_common.use_alternate,
7576 prefs_common.use_both_dicts,
7577 GTK_TEXT_VIEW(text),
7578 GTK_WINDOW(compose->window),
7579 compose_dict_changed,
7580 compose_spell_menu_changed,
7583 alertpanel_error(_("Spell checker could not "
7585 gtkaspell_checkers_strerror());
7586 gtkaspell_checkers_reset_error();
7588 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7592 compose->gtkaspell = gtkaspell;
7593 compose_spell_menu_changed(compose);
7594 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7597 compose_select_account(compose, account, TRUE);
7599 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7600 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7602 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7603 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7605 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7606 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7608 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7609 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7611 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7612 if (account->protocol != A_NNTP)
7613 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7614 prefs_common_translated_header_name("To:"));
7616 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7617 prefs_common_translated_header_name("Newsgroups:"));
7619 addressbook_set_target_compose(compose);
7621 if (mode != COMPOSE_REDIRECT)
7622 compose_set_template_menu(compose);
7624 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7627 compose_list = g_list_append(compose_list, compose);
7629 if (!prefs_common.show_ruler)
7630 gtk_widget_hide(ruler_hbox);
7632 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7635 compose->priority = PRIORITY_NORMAL;
7636 compose_update_priority_menu_item(compose);
7638 compose_set_out_encoding(compose);
7641 compose_update_actions_menu(compose);
7643 /* Privacy Systems menu */
7644 compose_update_privacy_systems_menu(compose);
7646 activate_privacy_system(compose, account, TRUE);
7647 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7649 gtk_widget_realize(window);
7651 gtk_widget_show(window);
7653 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7654 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7661 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7666 GtkWidget *optmenubox;
7669 GtkWidget *from_name = NULL;
7670 #if !(GTK_CHECK_VERSION(2,12,0))
7671 GtkTooltips *tips = compose->tooltips;
7674 gint num = 0, def_menu = 0;
7676 accounts = account_get_list();
7677 cm_return_val_if_fail(accounts != NULL, NULL);
7679 optmenubox = gtk_event_box_new();
7680 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7681 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7683 hbox = gtk_hbox_new(FALSE, 6);
7684 from_name = gtk_entry_new();
7686 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7687 G_CALLBACK(compose_grab_focus_cb), compose);
7689 for (; accounts != NULL; accounts = accounts->next, num++) {
7690 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7691 gchar *name, *from = NULL;
7693 if (ac == compose->account) def_menu = num;
7695 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7698 if (ac == compose->account) {
7699 if (ac->name && *ac->name) {
7701 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7702 from = g_strdup_printf("%s <%s>",
7704 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7706 from = g_strdup_printf("%s",
7708 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7711 COMBOBOX_ADD(menu, name, ac->account_id);
7716 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7718 g_signal_connect(G_OBJECT(optmenu), "changed",
7719 G_CALLBACK(account_activated),
7721 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7722 G_CALLBACK(compose_entry_popup_extend),
7725 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7726 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7728 CLAWS_SET_TIP(optmenubox,
7729 _("Account to use for this email"));
7730 CLAWS_SET_TIP(from_name,
7731 _("Sender address to be used"));
7733 compose->account_combo = optmenu;
7734 compose->from_name = from_name;
7739 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7741 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7742 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7743 Compose *compose = (Compose *) data;
7745 compose->priority = value;
7749 static void compose_reply_change_mode(Compose *compose,
7752 gboolean was_modified = compose->modified;
7754 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7756 cm_return_if_fail(compose->replyinfo != NULL);
7758 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7760 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7762 if (action == COMPOSE_REPLY_TO_ALL)
7764 if (action == COMPOSE_REPLY_TO_SENDER)
7766 if (action == COMPOSE_REPLY_TO_LIST)
7769 compose_remove_header_entries(compose);
7770 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7771 if (compose->account->set_autocc && compose->account->auto_cc)
7772 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7774 if (compose->account->set_autobcc && compose->account->auto_bcc)
7775 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7777 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7778 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7779 compose_show_first_last_header(compose, TRUE);
7780 compose->modified = was_modified;
7781 compose_set_title(compose);
7784 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7786 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7787 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7788 Compose *compose = (Compose *) data;
7791 compose_reply_change_mode(compose, value);
7794 static void compose_update_priority_menu_item(Compose * compose)
7796 GtkWidget *menuitem = NULL;
7797 switch (compose->priority) {
7798 case PRIORITY_HIGHEST:
7799 menuitem = gtk_ui_manager_get_widget
7800 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7803 menuitem = gtk_ui_manager_get_widget
7804 (compose->ui_manager, "/Menu/Options/Priority/High");
7806 case PRIORITY_NORMAL:
7807 menuitem = gtk_ui_manager_get_widget
7808 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7811 menuitem = gtk_ui_manager_get_widget
7812 (compose->ui_manager, "/Menu/Options/Priority/Low");
7814 case PRIORITY_LOWEST:
7815 menuitem = gtk_ui_manager_get_widget
7816 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7819 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7822 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7824 Compose *compose = (Compose *) data;
7826 gboolean can_sign = FALSE, can_encrypt = FALSE;
7828 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7830 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7833 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7834 g_free(compose->privacy_system);
7835 compose->privacy_system = NULL;
7836 if (systemid != NULL) {
7837 compose->privacy_system = g_strdup(systemid);
7839 can_sign = privacy_system_can_sign(systemid);
7840 can_encrypt = privacy_system_can_encrypt(systemid);
7843 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7845 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7846 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7849 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7851 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7852 GtkWidget *menuitem = NULL;
7854 gboolean can_sign = FALSE, can_encrypt = FALSE;
7855 gboolean found = FALSE;
7857 if (compose->privacy_system != NULL) {
7859 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7860 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7861 cm_return_if_fail(menuitem != NULL);
7863 amenu = GTK_MENU_SHELL(menuitem)->children;
7865 while (amenu != NULL) {
7866 GList *alist = amenu->next;
7868 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7869 if (systemid != NULL) {
7870 if (strcmp(systemid, compose->privacy_system) == 0 &&
7871 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7872 menuitem = GTK_WIDGET(amenu->data);
7874 can_sign = privacy_system_can_sign(systemid);
7875 can_encrypt = privacy_system_can_encrypt(systemid);
7879 } else if (strlen(compose->privacy_system) == 0 &&
7880 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7881 menuitem = GTK_WIDGET(amenu->data);
7884 can_encrypt = FALSE;
7891 if (menuitem != NULL)
7892 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7894 if (warn && !found && strlen(compose->privacy_system)) {
7895 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7896 "will not be able to sign or encrypt this message."),
7897 compose->privacy_system);
7901 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7902 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7905 static void compose_set_out_encoding(Compose *compose)
7907 CharSet out_encoding;
7908 const gchar *branch = NULL;
7909 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7911 switch(out_encoding) {
7912 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7913 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7914 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7915 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7916 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7917 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7918 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7919 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7920 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7921 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7922 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7923 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7924 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7925 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7926 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7927 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7928 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7929 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7930 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7931 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7932 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7933 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7934 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7935 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
7936 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7937 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7938 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7939 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7940 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7941 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7942 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7943 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7944 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7946 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7949 static void compose_set_template_menu(Compose *compose)
7951 GSList *tmpl_list, *cur;
7955 tmpl_list = template_get_config();
7957 menu = gtk_menu_new();
7959 gtk_menu_set_accel_group (GTK_MENU (menu),
7960 gtk_ui_manager_get_accel_group(compose->ui_manager));
7961 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7962 Template *tmpl = (Template *)cur->data;
7963 gchar *accel_path = NULL;
7964 item = gtk_menu_item_new_with_label(tmpl->name);
7965 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7966 g_signal_connect(G_OBJECT(item), "activate",
7967 G_CALLBACK(compose_template_activate_cb),
7969 g_object_set_data(G_OBJECT(item), "template", tmpl);
7970 gtk_widget_show(item);
7971 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7972 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7976 gtk_widget_show(menu);
7977 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7980 void compose_update_actions_menu(Compose *compose)
7982 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7985 static void compose_update_privacy_systems_menu(Compose *compose)
7987 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7988 GSList *systems, *cur;
7990 GtkWidget *system_none;
7992 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7993 GtkWidget *privacy_menu = gtk_menu_new();
7995 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7996 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7998 g_signal_connect(G_OBJECT(system_none), "activate",
7999 G_CALLBACK(compose_set_privacy_system_cb), compose);
8001 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8002 gtk_widget_show(system_none);
8004 systems = privacy_get_system_ids();
8005 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8006 gchar *systemid = cur->data;
8008 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8009 widget = gtk_radio_menu_item_new_with_label(group,
8010 privacy_system_get_name(systemid));
8011 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8012 g_strdup(systemid), g_free);
8013 g_signal_connect(G_OBJECT(widget), "activate",
8014 G_CALLBACK(compose_set_privacy_system_cb), compose);
8016 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8017 gtk_widget_show(widget);
8020 g_slist_free(systems);
8021 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8022 gtk_widget_show_all(privacy_menu);
8023 gtk_widget_show_all(privacy_menuitem);
8026 void compose_reflect_prefs_all(void)
8031 for (cur = compose_list; cur != NULL; cur = cur->next) {
8032 compose = (Compose *)cur->data;
8033 compose_set_template_menu(compose);
8037 void compose_reflect_prefs_pixmap_theme(void)
8042 for (cur = compose_list; cur != NULL; cur = cur->next) {
8043 compose = (Compose *)cur->data;
8044 toolbar_update(TOOLBAR_COMPOSE, compose);
8048 static const gchar *compose_quote_char_from_context(Compose *compose)
8050 const gchar *qmark = NULL;
8052 cm_return_val_if_fail(compose != NULL, NULL);
8054 switch (compose->mode) {
8055 /* use forward-specific quote char */
8056 case COMPOSE_FORWARD:
8057 case COMPOSE_FORWARD_AS_ATTACH:
8058 case COMPOSE_FORWARD_INLINE:
8059 if (compose->folder && compose->folder->prefs &&
8060 compose->folder->prefs->forward_with_format)
8061 qmark = compose->folder->prefs->forward_quotemark;
8062 else if (compose->account->forward_with_format)
8063 qmark = compose->account->forward_quotemark;
8065 qmark = prefs_common.fw_quotemark;
8068 /* use reply-specific quote char in all other modes */
8070 if (compose->folder && compose->folder->prefs &&
8071 compose->folder->prefs->reply_with_format)
8072 qmark = compose->folder->prefs->reply_quotemark;
8073 else if (compose->account->reply_with_format)
8074 qmark = compose->account->reply_quotemark;
8076 qmark = prefs_common.quotemark;
8080 if (qmark == NULL || *qmark == '\0')
8086 static void compose_template_apply(Compose *compose, Template *tmpl,
8090 GtkTextBuffer *buffer;
8094 gchar *parsed_str = NULL;
8095 gint cursor_pos = 0;
8096 const gchar *err_msg = _("The body of the template has an error at line %d.");
8099 /* process the body */
8101 text = GTK_TEXT_VIEW(compose->text);
8102 buffer = gtk_text_view_get_buffer(text);
8105 qmark = compose_quote_char_from_context(compose);
8107 if (compose->replyinfo != NULL) {
8110 gtk_text_buffer_set_text(buffer, "", -1);
8111 mark = gtk_text_buffer_get_insert(buffer);
8112 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8114 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8115 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8117 } else if (compose->fwdinfo != NULL) {
8120 gtk_text_buffer_set_text(buffer, "", -1);
8121 mark = gtk_text_buffer_get_insert(buffer);
8122 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8124 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8125 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8128 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8130 GtkTextIter start, end;
8133 gtk_text_buffer_get_start_iter(buffer, &start);
8134 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8135 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8137 /* clear the buffer now */
8139 gtk_text_buffer_set_text(buffer, "", -1);
8141 parsed_str = compose_quote_fmt(compose, dummyinfo,
8142 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8143 procmsg_msginfo_free( dummyinfo );
8149 gtk_text_buffer_set_text(buffer, "", -1);
8150 mark = gtk_text_buffer_get_insert(buffer);
8151 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8154 if (replace && parsed_str && compose->account->auto_sig)
8155 compose_insert_sig(compose, FALSE);
8157 if (replace && parsed_str) {
8158 gtk_text_buffer_get_start_iter(buffer, &iter);
8159 gtk_text_buffer_place_cursor(buffer, &iter);
8163 cursor_pos = quote_fmt_get_cursor_pos();
8164 compose->set_cursor_pos = cursor_pos;
8165 if (cursor_pos == -1)
8167 gtk_text_buffer_get_start_iter(buffer, &iter);
8168 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8169 gtk_text_buffer_place_cursor(buffer, &iter);
8172 /* process the other fields */
8174 compose_template_apply_fields(compose, tmpl);
8175 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8176 quote_fmt_reset_vartable();
8177 compose_changed_cb(NULL, compose);
8180 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8181 gtkaspell_highlight_all(compose->gtkaspell);
8185 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8187 MsgInfo* dummyinfo = NULL;
8188 MsgInfo *msginfo = NULL;
8191 if (compose->replyinfo != NULL)
8192 msginfo = compose->replyinfo;
8193 else if (compose->fwdinfo != NULL)
8194 msginfo = compose->fwdinfo;
8196 dummyinfo = compose_msginfo_new_from_compose(compose);
8197 msginfo = dummyinfo;
8200 if (tmpl->from && *tmpl->from != '\0') {
8202 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8203 compose->gtkaspell);
8205 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8207 quote_fmt_scan_string(tmpl->from);
8210 buf = quote_fmt_get_buffer();
8212 alertpanel_error(_("Template From format error."));
8214 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8218 if (tmpl->to && *tmpl->to != '\0') {
8220 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8221 compose->gtkaspell);
8223 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8225 quote_fmt_scan_string(tmpl->to);
8228 buf = quote_fmt_get_buffer();
8230 alertpanel_error(_("Template To format error."));
8232 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8236 if (tmpl->cc && *tmpl->cc != '\0') {
8238 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8239 compose->gtkaspell);
8241 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8243 quote_fmt_scan_string(tmpl->cc);
8246 buf = quote_fmt_get_buffer();
8248 alertpanel_error(_("Template Cc format error."));
8250 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8254 if (tmpl->bcc && *tmpl->bcc != '\0') {
8256 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8257 compose->gtkaspell);
8259 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8261 quote_fmt_scan_string(tmpl->bcc);
8264 buf = quote_fmt_get_buffer();
8266 alertpanel_error(_("Template Bcc format error."));
8268 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8272 /* process the subject */
8273 if (tmpl->subject && *tmpl->subject != '\0') {
8275 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8276 compose->gtkaspell);
8278 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8280 quote_fmt_scan_string(tmpl->subject);
8283 buf = quote_fmt_get_buffer();
8285 alertpanel_error(_("Template subject format error."));
8287 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8291 procmsg_msginfo_free( dummyinfo );
8294 static void compose_destroy(Compose *compose)
8296 GtkTextBuffer *buffer;
8297 GtkClipboard *clipboard;
8299 compose_list = g_list_remove(compose_list, compose);
8301 if (compose->updating) {
8302 debug_print("danger, not destroying anything now\n");
8303 compose->deferred_destroy = TRUE;
8306 /* NOTE: address_completion_end() does nothing with the window
8307 * however this may change. */
8308 address_completion_end(compose->window);
8310 slist_free_strings(compose->to_list);
8311 g_slist_free(compose->to_list);
8312 slist_free_strings(compose->newsgroup_list);
8313 g_slist_free(compose->newsgroup_list);
8314 slist_free_strings(compose->header_list);
8315 g_slist_free(compose->header_list);
8317 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8319 g_hash_table_destroy(compose->email_hashtable);
8321 procmsg_msginfo_free(compose->targetinfo);
8322 procmsg_msginfo_free(compose->replyinfo);
8323 procmsg_msginfo_free(compose->fwdinfo);
8325 g_free(compose->replyto);
8326 g_free(compose->cc);
8327 g_free(compose->bcc);
8328 g_free(compose->newsgroups);
8329 g_free(compose->followup_to);
8331 g_free(compose->ml_post);
8333 g_free(compose->inreplyto);
8334 g_free(compose->references);
8335 g_free(compose->msgid);
8336 g_free(compose->boundary);
8338 g_free(compose->redirect_filename);
8339 if (compose->undostruct)
8340 undo_destroy(compose->undostruct);
8342 g_free(compose->sig_str);
8344 g_free(compose->exteditor_file);
8346 g_free(compose->orig_charset);
8348 g_free(compose->privacy_system);
8350 if (addressbook_get_target_compose() == compose)
8351 addressbook_set_target_compose(NULL);
8354 if (compose->gtkaspell) {
8355 gtkaspell_delete(compose->gtkaspell);
8356 compose->gtkaspell = NULL;
8360 if (!compose->batch) {
8361 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8362 prefs_common.compose_height = compose->window->allocation.height;
8365 if (!gtk_widget_get_parent(compose->paned))
8366 gtk_widget_destroy(compose->paned);
8367 gtk_widget_destroy(compose->popupmenu);
8369 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8370 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8371 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8373 gtk_widget_destroy(compose->window);
8374 toolbar_destroy(compose->toolbar);
8375 g_free(compose->toolbar);
8376 g_mutex_free(compose->mutex);
8380 static void compose_attach_info_free(AttachInfo *ainfo)
8382 g_free(ainfo->file);
8383 g_free(ainfo->content_type);
8384 g_free(ainfo->name);
8388 static void compose_attach_update_label(Compose *compose)
8393 GtkTreeModel *model;
8398 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8399 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8400 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8404 while(gtk_tree_model_iter_next(model, &iter))
8407 text = g_strdup_printf("(%d)", i);
8408 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8412 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8414 Compose *compose = (Compose *)data;
8415 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8416 GtkTreeSelection *selection;
8418 GtkTreeModel *model;
8420 selection = gtk_tree_view_get_selection(tree_view);
8421 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8426 for (cur = sel; cur != NULL; cur = cur->next) {
8427 GtkTreePath *path = cur->data;
8428 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8431 gtk_tree_path_free(path);
8434 for (cur = sel; cur != NULL; cur = cur->next) {
8435 GtkTreeRowReference *ref = cur->data;
8436 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8439 if (gtk_tree_model_get_iter(model, &iter, path))
8440 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8442 gtk_tree_path_free(path);
8443 gtk_tree_row_reference_free(ref);
8447 compose_attach_update_label(compose);
8450 static struct _AttachProperty
8453 GtkWidget *mimetype_entry;
8454 GtkWidget *encoding_optmenu;
8455 GtkWidget *path_entry;
8456 GtkWidget *filename_entry;
8458 GtkWidget *cancel_btn;
8461 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8463 gtk_tree_path_free((GtkTreePath *)ptr);
8466 static void compose_attach_property(GtkAction *action, gpointer data)
8468 Compose *compose = (Compose *)data;
8469 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8471 GtkComboBox *optmenu;
8472 GtkTreeSelection *selection;
8474 GtkTreeModel *model;
8477 static gboolean cancelled;
8479 /* only if one selected */
8480 selection = gtk_tree_view_get_selection(tree_view);
8481 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8484 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8488 path = (GtkTreePath *) sel->data;
8489 gtk_tree_model_get_iter(model, &iter, path);
8490 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8493 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8499 if (!attach_prop.window)
8500 compose_attach_property_create(&cancelled);
8501 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8502 gtk_widget_grab_focus(attach_prop.ok_btn);
8503 gtk_widget_show(attach_prop.window);
8504 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8506 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8507 if (ainfo->encoding == ENC_UNKNOWN)
8508 combobox_select_by_data(optmenu, ENC_BASE64);
8510 combobox_select_by_data(optmenu, ainfo->encoding);
8512 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8513 ainfo->content_type ? ainfo->content_type : "");
8514 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8515 ainfo->file ? ainfo->file : "");
8516 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8517 ainfo->name ? ainfo->name : "");
8520 const gchar *entry_text;
8522 gchar *cnttype = NULL;
8529 gtk_widget_hide(attach_prop.window);
8530 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8535 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8536 if (*entry_text != '\0') {
8539 text = g_strstrip(g_strdup(entry_text));
8540 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8541 cnttype = g_strdup(text);
8544 alertpanel_error(_("Invalid MIME type."));
8550 ainfo->encoding = combobox_get_active_data(optmenu);
8552 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8553 if (*entry_text != '\0') {
8554 if (is_file_exist(entry_text) &&
8555 (size = get_file_size(entry_text)) > 0)
8556 file = g_strdup(entry_text);
8559 (_("File doesn't exist or is empty."));
8565 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8566 if (*entry_text != '\0') {
8567 g_free(ainfo->name);
8568 ainfo->name = g_strdup(entry_text);
8572 g_free(ainfo->content_type);
8573 ainfo->content_type = cnttype;
8576 g_free(ainfo->file);
8580 ainfo->size = (goffset)size;
8582 /* update tree store */
8583 text = to_human_readable(ainfo->size);
8584 gtk_tree_model_get_iter(model, &iter, path);
8585 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8586 COL_MIMETYPE, ainfo->content_type,
8588 COL_NAME, ainfo->name,
8594 gtk_tree_path_free(path);
8597 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8599 label = gtk_label_new(str); \
8600 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8601 GTK_FILL, 0, 0, 0); \
8602 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8604 entry = gtk_entry_new(); \
8605 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8606 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8609 static void compose_attach_property_create(gboolean *cancelled)
8615 GtkWidget *mimetype_entry;
8618 GtkListStore *optmenu_menu;
8619 GtkWidget *path_entry;
8620 GtkWidget *filename_entry;
8623 GtkWidget *cancel_btn;
8624 GList *mime_type_list, *strlist;
8627 debug_print("Creating attach_property window...\n");
8629 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8630 gtk_widget_set_size_request(window, 480, -1);
8631 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8632 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8633 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8634 g_signal_connect(G_OBJECT(window), "delete_event",
8635 G_CALLBACK(attach_property_delete_event),
8637 g_signal_connect(G_OBJECT(window), "key_press_event",
8638 G_CALLBACK(attach_property_key_pressed),
8641 vbox = gtk_vbox_new(FALSE, 8);
8642 gtk_container_add(GTK_CONTAINER(window), vbox);
8644 table = gtk_table_new(4, 2, FALSE);
8645 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8646 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8647 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8649 label = gtk_label_new(_("MIME type"));
8650 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8652 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8653 mimetype_entry = gtk_combo_box_entry_new_text();
8654 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8655 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8657 /* stuff with list */
8658 mime_type_list = procmime_get_mime_type_list();
8660 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8661 MimeType *type = (MimeType *) mime_type_list->data;
8664 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8666 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8669 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8670 (GCompareFunc)strcmp2);
8673 for (mime_type_list = strlist; mime_type_list != NULL;
8674 mime_type_list = mime_type_list->next) {
8675 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8676 g_free(mime_type_list->data);
8678 g_list_free(strlist);
8679 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8680 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8682 label = gtk_label_new(_("Encoding"));
8683 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8685 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8687 hbox = gtk_hbox_new(FALSE, 0);
8688 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8689 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8691 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8692 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8694 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8695 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8696 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8697 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8698 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8700 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8702 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8703 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8705 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8706 &ok_btn, GTK_STOCK_OK,
8708 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8709 gtk_widget_grab_default(ok_btn);
8711 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8712 G_CALLBACK(attach_property_ok),
8714 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8715 G_CALLBACK(attach_property_cancel),
8718 gtk_widget_show_all(vbox);
8720 attach_prop.window = window;
8721 attach_prop.mimetype_entry = mimetype_entry;
8722 attach_prop.encoding_optmenu = optmenu;
8723 attach_prop.path_entry = path_entry;
8724 attach_prop.filename_entry = filename_entry;
8725 attach_prop.ok_btn = ok_btn;
8726 attach_prop.cancel_btn = cancel_btn;
8729 #undef SET_LABEL_AND_ENTRY
8731 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8737 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8743 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8744 gboolean *cancelled)
8752 static gboolean attach_property_key_pressed(GtkWidget *widget,
8754 gboolean *cancelled)
8756 if (event && event->keyval == GDK_Escape) {
8760 if (event && event->keyval == GDK_Return) {
8768 static void compose_exec_ext_editor(Compose *compose)
8775 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8776 G_DIR_SEPARATOR, compose);
8778 if (pipe(pipe_fds) < 0) {
8784 if ((pid = fork()) < 0) {
8791 /* close the write side of the pipe */
8794 compose->exteditor_file = g_strdup(tmp);
8795 compose->exteditor_pid = pid;
8797 compose_set_ext_editor_sensitive(compose, FALSE);
8800 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8802 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8804 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8808 } else { /* process-monitoring process */
8814 /* close the read side of the pipe */
8817 if (compose_write_body_to_file(compose, tmp) < 0) {
8818 fd_write_all(pipe_fds[1], "2\n", 2);
8822 pid_ed = compose_exec_ext_editor_real(tmp);
8824 fd_write_all(pipe_fds[1], "1\n", 2);
8828 /* wait until editor is terminated */
8829 waitpid(pid_ed, NULL, 0);
8831 fd_write_all(pipe_fds[1], "0\n", 2);
8838 #endif /* G_OS_UNIX */
8842 static gint compose_exec_ext_editor_real(const gchar *file)
8849 cm_return_val_if_fail(file != NULL, -1);
8851 if ((pid = fork()) < 0) {
8856 if (pid != 0) return pid;
8858 /* grandchild process */
8860 if (setpgid(0, getppid()))
8863 if (prefs_common_get_ext_editor_cmd() &&
8864 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8865 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8866 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8868 if (prefs_common_get_ext_editor_cmd())
8869 g_warning("External editor command-line is invalid: '%s'\n",
8870 prefs_common_get_ext_editor_cmd());
8871 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8874 cmdline = strsplit_with_quote(buf, " ", 1024);
8875 execvp(cmdline[0], cmdline);
8878 g_strfreev(cmdline);
8883 static gboolean compose_ext_editor_kill(Compose *compose)
8885 pid_t pgid = compose->exteditor_pid * -1;
8888 ret = kill(pgid, 0);
8890 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8894 msg = g_strdup_printf
8895 (_("The external editor is still working.\n"
8896 "Force terminating the process?\n"
8897 "process group id: %d"), -pgid);
8898 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8899 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8903 if (val == G_ALERTALTERNATE) {
8904 g_source_remove(compose->exteditor_tag);
8905 g_io_channel_shutdown(compose->exteditor_ch,
8907 g_io_channel_unref(compose->exteditor_ch);
8909 if (kill(pgid, SIGTERM) < 0) perror("kill");
8910 waitpid(compose->exteditor_pid, NULL, 0);
8912 g_warning("Terminated process group id: %d", -pgid);
8913 g_warning("Temporary file: %s",
8914 compose->exteditor_file);
8916 compose_set_ext_editor_sensitive(compose, TRUE);
8918 g_free(compose->exteditor_file);
8919 compose->exteditor_file = NULL;
8920 compose->exteditor_pid = -1;
8921 compose->exteditor_ch = NULL;
8922 compose->exteditor_tag = -1;
8930 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8934 Compose *compose = (Compose *)data;
8937 debug_print(_("Compose: input from monitoring process\n"));
8939 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8941 g_io_channel_shutdown(source, FALSE, NULL);
8942 g_io_channel_unref(source);
8944 waitpid(compose->exteditor_pid, NULL, 0);
8946 if (buf[0] == '0') { /* success */
8947 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8948 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8950 gtk_text_buffer_set_text(buffer, "", -1);
8951 compose_insert_file(compose, compose->exteditor_file);
8952 compose_changed_cb(NULL, compose);
8953 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8955 if (claws_unlink(compose->exteditor_file) < 0)
8956 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8957 } else if (buf[0] == '1') { /* failed */
8958 g_warning("Couldn't exec external editor\n");
8959 if (claws_unlink(compose->exteditor_file) < 0)
8960 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8961 } else if (buf[0] == '2') {
8962 g_warning("Couldn't write to file\n");
8963 } else if (buf[0] == '3') {
8964 g_warning("Pipe read failed\n");
8967 compose_set_ext_editor_sensitive(compose, TRUE);
8969 g_free(compose->exteditor_file);
8970 compose->exteditor_file = NULL;
8971 compose->exteditor_pid = -1;
8972 compose->exteditor_ch = NULL;
8973 compose->exteditor_tag = -1;
8978 static void compose_set_ext_editor_sensitive(Compose *compose,
8981 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8982 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8983 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8984 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8985 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8989 gtk_widget_set_sensitive(compose->text, sensitive);
8990 if (compose->toolbar->send_btn)
8991 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8992 if (compose->toolbar->sendl_btn)
8993 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8994 if (compose->toolbar->draft_btn)
8995 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8996 if (compose->toolbar->insert_btn)
8997 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8998 if (compose->toolbar->sig_btn)
8999 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9000 if (compose->toolbar->exteditor_btn)
9001 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9002 if (compose->toolbar->linewrap_current_btn)
9003 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9004 if (compose->toolbar->linewrap_all_btn)
9005 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9007 #endif /* G_OS_UNIX */
9010 * compose_undo_state_changed:
9012 * Change the sensivity of the menuentries undo and redo
9014 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9015 gint redo_state, gpointer data)
9017 Compose *compose = (Compose *)data;
9019 switch (undo_state) {
9020 case UNDO_STATE_TRUE:
9021 if (!undostruct->undo_state) {
9022 undostruct->undo_state = TRUE;
9023 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9026 case UNDO_STATE_FALSE:
9027 if (undostruct->undo_state) {
9028 undostruct->undo_state = FALSE;
9029 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9032 case UNDO_STATE_UNCHANGED:
9034 case UNDO_STATE_REFRESH:
9035 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9038 g_warning("Undo state not recognized");
9042 switch (redo_state) {
9043 case UNDO_STATE_TRUE:
9044 if (!undostruct->redo_state) {
9045 undostruct->redo_state = TRUE;
9046 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9049 case UNDO_STATE_FALSE:
9050 if (undostruct->redo_state) {
9051 undostruct->redo_state = FALSE;
9052 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9055 case UNDO_STATE_UNCHANGED:
9057 case UNDO_STATE_REFRESH:
9058 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9061 g_warning("Redo state not recognized");
9066 /* callback functions */
9068 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9069 * includes "non-client" (windows-izm) in calculation, so this calculation
9070 * may not be accurate.
9072 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9073 GtkAllocation *allocation,
9074 GtkSHRuler *shruler)
9076 if (prefs_common.show_ruler) {
9077 gint char_width = 0, char_height = 0;
9078 gint line_width_in_chars;
9080 gtkut_get_font_size(GTK_WIDGET(widget),
9081 &char_width, &char_height);
9082 line_width_in_chars =
9083 (allocation->width - allocation->x) / char_width;
9085 /* got the maximum */
9086 gtk_ruler_set_range(GTK_RULER(shruler),
9087 0.0, line_width_in_chars, 0,
9088 /*line_width_in_chars*/ char_width);
9097 ComposePrefType type;
9098 gboolean entry_marked;
9101 static void account_activated(GtkComboBox *optmenu, gpointer data)
9103 Compose *compose = (Compose *)data;
9106 gchar *folderidentifier;
9107 gint account_id = 0;
9110 GSList *list, *saved_list = NULL;
9111 HeaderEntryState *state;
9112 GtkRcStyle *style = NULL;
9113 static GdkColor yellow;
9114 static gboolean color_set = FALSE;
9116 /* Get ID of active account in the combo box */
9117 menu = gtk_combo_box_get_model(optmenu);
9118 gtk_combo_box_get_active_iter(optmenu, &iter);
9119 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9121 ac = account_find_from_id(account_id);
9122 cm_return_if_fail(ac != NULL);
9124 if (ac != compose->account) {
9125 compose_select_account(compose, ac, FALSE);
9127 for (list = compose->header_list; list; list = list->next) {
9128 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9130 if (hentry->type == PREF_ACCOUNT || !list->next) {
9131 compose_destroy_headerentry(compose, hentry);
9135 state = g_malloc0(sizeof(HeaderEntryState));
9136 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9137 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9138 state->entry = gtk_editable_get_chars(
9139 GTK_EDITABLE(hentry->entry), 0, -1);
9140 state->type = hentry->type;
9143 gdk_color_parse("#f5f6be", &yellow);
9144 color_set = gdk_colormap_alloc_color(
9145 gdk_colormap_get_system(),
9146 &yellow, FALSE, TRUE);
9149 style = gtk_widget_get_modifier_style(hentry->entry);
9150 state->entry_marked = gdk_color_equal(&yellow,
9151 &style->base[GTK_STATE_NORMAL]);
9153 saved_list = g_slist_append(saved_list, state);
9154 compose_destroy_headerentry(compose, hentry);
9157 compose->header_last = NULL;
9158 g_slist_free(compose->header_list);
9159 compose->header_list = NULL;
9160 compose->header_nextrow = 1;
9161 compose_create_header_entry(compose);
9163 if (ac->set_autocc && ac->auto_cc)
9164 compose_entry_append(compose, ac->auto_cc,
9165 COMPOSE_CC, PREF_ACCOUNT);
9167 if (ac->set_autobcc && ac->auto_bcc)
9168 compose_entry_append(compose, ac->auto_bcc,
9169 COMPOSE_BCC, PREF_ACCOUNT);
9171 if (ac->set_autoreplyto && ac->auto_replyto)
9172 compose_entry_append(compose, ac->auto_replyto,
9173 COMPOSE_REPLYTO, PREF_ACCOUNT);
9175 for (list = saved_list; list; list = list->next) {
9176 state = (HeaderEntryState *) list->data;
9178 compose_add_header_entry(compose, state->header,
9179 state->entry, state->type);
9180 if (state->entry_marked)
9181 compose_entry_mark_default_to(compose, state->entry);
9183 g_free(state->header);
9184 g_free(state->entry);
9187 g_slist_free(saved_list);
9189 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9190 (ac->protocol == A_NNTP) ?
9191 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9194 /* Set message save folder */
9195 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9196 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9198 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9199 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9201 compose_set_save_to(compose, NULL);
9202 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9203 folderidentifier = folder_item_get_identifier(account_get_special_folder
9204 (compose->account, F_OUTBOX));
9205 compose_set_save_to(compose, folderidentifier);
9206 g_free(folderidentifier);
9210 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9211 GtkTreeViewColumn *column, Compose *compose)
9213 compose_attach_property(NULL, compose);
9216 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9219 Compose *compose = (Compose *)data;
9220 GtkTreeSelection *attach_selection;
9221 gint attach_nr_selected;
9223 if (!event) return FALSE;
9225 if (event->button == 3) {
9226 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9227 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9229 if (attach_nr_selected > 0)
9231 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9232 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9234 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9235 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9238 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9239 NULL, NULL, event->button, event->time);
9246 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9249 Compose *compose = (Compose *)data;
9251 if (!event) return FALSE;
9253 switch (event->keyval) {
9255 compose_attach_remove_selected(NULL, compose);
9261 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9263 toolbar_comp_set_sensitive(compose, allow);
9264 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9265 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9267 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9269 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9270 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9271 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9273 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9277 static void compose_send_cb(GtkAction *action, gpointer data)
9279 Compose *compose = (Compose *)data;
9281 if (prefs_common.work_offline &&
9282 !inc_offline_should_override(TRUE,
9283 _("Claws Mail needs network access in order "
9284 "to send this email.")))
9287 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9288 g_source_remove(compose->draft_timeout_tag);
9289 compose->draft_timeout_tag = -1;
9292 compose_send(compose);
9295 static void compose_send_later_cb(GtkAction *action, gpointer data)
9297 Compose *compose = (Compose *)data;
9301 compose_allow_user_actions(compose, FALSE);
9302 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9303 compose_allow_user_actions(compose, TRUE);
9307 compose_close(compose);
9308 } else if (val == -1) {
9309 alertpanel_error(_("Could not queue message."));
9310 } else if (val == -2) {
9311 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9312 } else if (val == -3) {
9313 if (privacy_peek_error())
9314 alertpanel_error(_("Could not queue message for sending:\n\n"
9315 "Signature failed: %s"), privacy_get_error());
9316 } else if (val == -4) {
9317 alertpanel_error(_("Could not queue message for sending:\n\n"
9318 "Charset conversion failed."));
9319 } else if (val == -5) {
9320 alertpanel_error(_("Could not queue message for sending:\n\n"
9321 "Couldn't get recipient encryption key."));
9322 } else if (val == -6) {
9325 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9328 #define DRAFTED_AT_EXIT "drafted_at_exit"
9329 static void compose_register_draft(MsgInfo *info)
9331 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9332 DRAFTED_AT_EXIT, NULL);
9333 FILE *fp = g_fopen(filepath, "ab");
9336 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9344 gboolean compose_draft (gpointer data, guint action)
9346 Compose *compose = (Compose *)data;
9350 MsgFlags flag = {0, 0};
9351 static gboolean lock = FALSE;
9352 MsgInfo *newmsginfo;
9354 gboolean target_locked = FALSE;
9355 gboolean err = FALSE;
9357 if (lock) return FALSE;
9359 if (compose->sending)
9362 draft = account_get_special_folder(compose->account, F_DRAFT);
9363 cm_return_val_if_fail(draft != NULL, FALSE);
9365 if (!g_mutex_trylock(compose->mutex)) {
9366 /* we don't want to lock the mutex once it's available,
9367 * because as the only other part of compose.c locking
9368 * it is compose_close - which means once unlocked,
9369 * the compose struct will be freed */
9370 debug_print("couldn't lock mutex, probably sending\n");
9376 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9377 G_DIR_SEPARATOR, compose);
9378 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9379 FILE_OP_ERROR(tmp, "fopen");
9383 /* chmod for security */
9384 if (change_file_mode_rw(fp, tmp) < 0) {
9385 FILE_OP_ERROR(tmp, "chmod");
9386 g_warning("can't change file mode\n");
9389 /* Save draft infos */
9390 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9391 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9393 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9394 gchar *savefolderid;
9396 savefolderid = compose_get_save_to(compose);
9397 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9398 g_free(savefolderid);
9400 if (compose->return_receipt) {
9401 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9403 if (compose->privacy_system) {
9404 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9405 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9406 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9409 /* Message-ID of message replying to */
9410 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9413 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9414 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9417 /* Message-ID of message forwarding to */
9418 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9421 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9422 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9426 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9427 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9429 /* end of headers */
9430 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9437 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9441 if (fclose(fp) == EOF) {
9445 if (compose->targetinfo) {
9446 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9447 flag.perm_flags = target_locked?MSG_LOCKED:0;
9449 flag.tmp_flags = MSG_DRAFT;
9451 folder_item_scan(draft);
9452 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9453 MsgInfo *tmpinfo = NULL;
9454 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9455 if (compose->msgid) {
9456 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9459 msgnum = tmpinfo->msgnum;
9460 procmsg_msginfo_free(tmpinfo);
9461 debug_print("got draft msgnum %d from scanning\n", msgnum);
9463 debug_print("didn't get draft msgnum after scanning\n");
9466 debug_print("got draft msgnum %d from adding\n", msgnum);
9472 if (action != COMPOSE_AUTO_SAVE) {
9473 if (action != COMPOSE_DRAFT_FOR_EXIT)
9474 alertpanel_error(_("Could not save draft."));
9477 gtkut_window_popup(compose->window);
9478 val = alertpanel_full(_("Could not save draft"),
9479 _("Could not save draft.\n"
9480 "Do you want to cancel exit or discard this email?"),
9481 _("_Cancel exit"), _("_Discard email"), NULL,
9482 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9483 if (val == G_ALERTALTERNATE) {
9485 g_mutex_unlock(compose->mutex); /* must be done before closing */
9486 compose_close(compose);
9490 g_mutex_unlock(compose->mutex); /* must be done before closing */
9499 if (compose->mode == COMPOSE_REEDIT) {
9500 compose_remove_reedit_target(compose, TRUE);
9503 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9506 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9508 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9510 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9511 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9512 procmsg_msginfo_set_flags(newmsginfo, 0,
9513 MSG_HAS_ATTACHMENT);
9515 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9516 compose_register_draft(newmsginfo);
9518 procmsg_msginfo_free(newmsginfo);
9521 folder_item_scan(draft);
9523 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9525 g_mutex_unlock(compose->mutex); /* must be done before closing */
9526 compose_close(compose);
9532 path = folder_item_fetch_msg(draft, msgnum);
9534 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9537 if (g_stat(path, &s) < 0) {
9538 FILE_OP_ERROR(path, "stat");
9544 procmsg_msginfo_free(compose->targetinfo);
9545 compose->targetinfo = procmsg_msginfo_new();
9546 compose->targetinfo->msgnum = msgnum;
9547 compose->targetinfo->size = (goffset)s.st_size;
9548 compose->targetinfo->mtime = s.st_mtime;
9549 compose->targetinfo->folder = draft;
9551 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9552 compose->mode = COMPOSE_REEDIT;
9554 if (action == COMPOSE_AUTO_SAVE) {
9555 compose->autosaved_draft = compose->targetinfo;
9557 compose->modified = FALSE;
9558 compose_set_title(compose);
9562 g_mutex_unlock(compose->mutex);
9566 void compose_clear_exit_drafts(void)
9568 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9569 DRAFTED_AT_EXIT, NULL);
9570 if (is_file_exist(filepath))
9571 claws_unlink(filepath);
9576 void compose_reopen_exit_drafts(void)
9578 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9579 DRAFTED_AT_EXIT, NULL);
9580 FILE *fp = g_fopen(filepath, "rb");
9584 while (fgets(buf, sizeof(buf), fp)) {
9585 gchar **parts = g_strsplit(buf, "\t", 2);
9586 const gchar *folder = parts[0];
9587 int msgnum = parts[1] ? atoi(parts[1]):-1;
9589 if (folder && *folder && msgnum > -1) {
9590 FolderItem *item = folder_find_item_from_identifier(folder);
9591 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9593 compose_reedit(info, FALSE);
9600 compose_clear_exit_drafts();
9603 static void compose_save_cb(GtkAction *action, gpointer data)
9605 Compose *compose = (Compose *)data;
9606 compose_draft(compose, COMPOSE_KEEP_EDITING);
9607 compose->rmode = COMPOSE_REEDIT;
9610 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9612 if (compose && file_list) {
9615 for ( tmp = file_list; tmp; tmp = tmp->next) {
9616 gchar *file = (gchar *) tmp->data;
9617 gchar *utf8_filename = conv_filename_to_utf8(file);
9618 compose_attach_append(compose, file, utf8_filename, NULL);
9619 compose_changed_cb(NULL, compose);
9624 g_free(utf8_filename);
9629 static void compose_attach_cb(GtkAction *action, gpointer data)
9631 Compose *compose = (Compose *)data;
9634 if (compose->redirect_filename != NULL)
9637 file_list = filesel_select_multiple_files_open(_("Select file"));
9640 compose_attach_from_list(compose, file_list, TRUE);
9641 g_list_free(file_list);
9645 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9647 Compose *compose = (Compose *)data;
9649 gint files_inserted = 0;
9651 file_list = filesel_select_multiple_files_open(_("Select file"));
9656 for ( tmp = file_list; tmp; tmp = tmp->next) {
9657 gchar *file = (gchar *) tmp->data;
9658 gchar *filedup = g_strdup(file);
9659 gchar *shortfile = g_path_get_basename(filedup);
9660 ComposeInsertResult res;
9661 /* insert the file if the file is short or if the user confirmed that
9662 he/she wants to insert the large file */
9663 res = compose_insert_file(compose, file);
9664 if (res == COMPOSE_INSERT_READ_ERROR) {
9665 alertpanel_error(_("File '%s' could not be read."), shortfile);
9666 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9667 alertpanel_error(_("File '%s' contained invalid characters\n"
9668 "for the current encoding, insertion may be incorrect."),
9670 } else if (res == COMPOSE_INSERT_SUCCESS)
9677 g_list_free(file_list);
9681 if (files_inserted > 0 && compose->gtkaspell &&
9682 compose->gtkaspell->check_while_typing)
9683 gtkaspell_highlight_all(compose->gtkaspell);
9687 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9689 Compose *compose = (Compose *)data;
9691 compose_insert_sig(compose, FALSE);
9694 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9698 Compose *compose = (Compose *)data;
9700 gtkut_widget_get_uposition(widget, &x, &y);
9701 if (!compose->batch) {
9702 prefs_common.compose_x = x;
9703 prefs_common.compose_y = y;
9705 if (compose->sending || compose->updating)
9707 compose_close_cb(NULL, compose);
9711 void compose_close_toolbar(Compose *compose)
9713 compose_close_cb(NULL, compose);
9716 static void compose_close_cb(GtkAction *action, gpointer data)
9718 Compose *compose = (Compose *)data;
9722 if (compose->exteditor_tag != -1) {
9723 if (!compose_ext_editor_kill(compose))
9728 if (compose->modified) {
9729 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9730 if (!g_mutex_trylock(compose->mutex)) {
9731 /* we don't want to lock the mutex once it's available,
9732 * because as the only other part of compose.c locking
9733 * it is compose_close - which means once unlocked,
9734 * the compose struct will be freed */
9735 debug_print("couldn't lock mutex, probably sending\n");
9739 val = alertpanel(_("Discard message"),
9740 _("This message has been modified. Discard it?"),
9741 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9743 val = alertpanel(_("Save changes"),
9744 _("This message has been modified. Save the latest changes?"),
9745 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9747 g_mutex_unlock(compose->mutex);
9749 case G_ALERTDEFAULT:
9750 if (prefs_common.autosave && !reedit)
9751 compose_remove_draft(compose);
9753 case G_ALERTALTERNATE:
9754 compose_draft(data, COMPOSE_QUIT_EDITING);
9761 compose_close(compose);
9764 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9766 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9767 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9768 Compose *compose = (Compose *) data;
9771 compose->out_encoding = (CharSet)value;
9774 static void compose_address_cb(GtkAction *action, gpointer data)
9776 Compose *compose = (Compose *)data;
9778 addressbook_open(compose);
9781 static void about_show_cb(GtkAction *action, gpointer data)
9786 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9788 Compose *compose = (Compose *)data;
9793 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9794 cm_return_if_fail(tmpl != NULL);
9796 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9798 val = alertpanel(_("Apply template"), msg,
9799 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9802 if (val == G_ALERTDEFAULT)
9803 compose_template_apply(compose, tmpl, TRUE);
9804 else if (val == G_ALERTALTERNATE)
9805 compose_template_apply(compose, tmpl, FALSE);
9808 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9810 Compose *compose = (Compose *)data;
9812 compose_exec_ext_editor(compose);
9815 static void compose_undo_cb(GtkAction *action, gpointer data)
9817 Compose *compose = (Compose *)data;
9818 gboolean prev_autowrap = compose->autowrap;
9820 compose->autowrap = FALSE;
9821 undo_undo(compose->undostruct);
9822 compose->autowrap = prev_autowrap;
9825 static void compose_redo_cb(GtkAction *action, gpointer data)
9827 Compose *compose = (Compose *)data;
9828 gboolean prev_autowrap = compose->autowrap;
9830 compose->autowrap = FALSE;
9831 undo_redo(compose->undostruct);
9832 compose->autowrap = prev_autowrap;
9835 static void entry_cut_clipboard(GtkWidget *entry)
9837 if (GTK_IS_EDITABLE(entry))
9838 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9839 else if (GTK_IS_TEXT_VIEW(entry))
9840 gtk_text_buffer_cut_clipboard(
9841 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9842 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9846 static void entry_copy_clipboard(GtkWidget *entry)
9848 if (GTK_IS_EDITABLE(entry))
9849 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9850 else if (GTK_IS_TEXT_VIEW(entry))
9851 gtk_text_buffer_copy_clipboard(
9852 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9853 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9856 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9857 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9859 if (GTK_IS_TEXT_VIEW(entry)) {
9860 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9861 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9862 GtkTextIter start_iter, end_iter;
9864 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9866 if (contents == NULL)
9869 /* we shouldn't delete the selection when middle-click-pasting, or we
9870 * can't mid-click-paste our own selection */
9871 if (clip != GDK_SELECTION_PRIMARY) {
9872 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9873 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9876 if (insert_place == NULL) {
9877 /* if insert_place isn't specified, insert at the cursor.
9878 * used for Ctrl-V pasting */
9879 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9880 start = gtk_text_iter_get_offset(&start_iter);
9881 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9883 /* if insert_place is specified, paste here.
9884 * used for mid-click-pasting */
9885 start = gtk_text_iter_get_offset(insert_place);
9886 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9887 if (prefs_common.primary_paste_unselects)
9888 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9892 /* paste unwrapped: mark the paste so it's not wrapped later */
9893 end = start + strlen(contents);
9894 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9895 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9896 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9897 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9898 /* rewrap paragraph now (after a mid-click-paste) */
9899 mark_start = gtk_text_buffer_get_insert(buffer);
9900 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9901 gtk_text_iter_backward_char(&start_iter);
9902 compose_beautify_paragraph(compose, &start_iter, TRUE);
9904 } else if (GTK_IS_EDITABLE(entry))
9905 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9907 compose->modified = TRUE;
9910 static void entry_allsel(GtkWidget *entry)
9912 if (GTK_IS_EDITABLE(entry))
9913 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9914 else if (GTK_IS_TEXT_VIEW(entry)) {
9915 GtkTextIter startiter, enditer;
9916 GtkTextBuffer *textbuf;
9918 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9919 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9920 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9922 gtk_text_buffer_move_mark_by_name(textbuf,
9923 "selection_bound", &startiter);
9924 gtk_text_buffer_move_mark_by_name(textbuf,
9925 "insert", &enditer);
9929 static void compose_cut_cb(GtkAction *action, gpointer data)
9931 Compose *compose = (Compose *)data;
9932 if (compose->focused_editable
9933 #ifndef GENERIC_UMPC
9934 && gtkut_widget_has_focus(compose->focused_editable)
9937 entry_cut_clipboard(compose->focused_editable);
9940 static void compose_copy_cb(GtkAction *action, gpointer data)
9942 Compose *compose = (Compose *)data;
9943 if (compose->focused_editable
9944 #ifndef GENERIC_UMPC
9945 && gtkut_widget_has_focus(compose->focused_editable)
9948 entry_copy_clipboard(compose->focused_editable);
9951 static void compose_paste_cb(GtkAction *action, gpointer data)
9953 Compose *compose = (Compose *)data;
9955 GtkTextBuffer *buffer;
9957 if (compose->focused_editable &&
9958 gtkut_widget_has_focus(compose->focused_editable))
9959 entry_paste_clipboard(compose, compose->focused_editable,
9960 prefs_common.linewrap_pastes,
9961 GDK_SELECTION_CLIPBOARD, NULL);
9965 if (gtkut_widget_has_focus(compose->text) &&
9966 compose->gtkaspell &&
9967 compose->gtkaspell->check_while_typing)
9968 gtkaspell_highlight_all(compose->gtkaspell);
9972 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9974 Compose *compose = (Compose *)data;
9975 gint wrap_quote = prefs_common.linewrap_quote;
9976 if (compose->focused_editable
9977 #ifndef GENERIC_UMPC
9978 && gtkut_widget_has_focus(compose->focused_editable)
9981 /* let text_insert() (called directly or at a later time
9982 * after the gtk_editable_paste_clipboard) know that
9983 * text is to be inserted as a quotation. implemented
9984 * by using a simple refcount... */
9985 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9986 G_OBJECT(compose->focused_editable),
9987 "paste_as_quotation"));
9988 g_object_set_data(G_OBJECT(compose->focused_editable),
9989 "paste_as_quotation",
9990 GINT_TO_POINTER(paste_as_quotation + 1));
9991 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9992 entry_paste_clipboard(compose, compose->focused_editable,
9993 prefs_common.linewrap_pastes,
9994 GDK_SELECTION_CLIPBOARD, NULL);
9995 prefs_common.linewrap_quote = wrap_quote;
9999 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10001 Compose *compose = (Compose *)data;
10002 gint prev_autowrap;
10003 GtkTextBuffer *buffer;
10005 if (compose->focused_editable
10006 #ifndef GENERIC_UMPC
10007 && gtkut_widget_has_focus(compose->focused_editable)
10010 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10011 GDK_SELECTION_CLIPBOARD, NULL);
10015 if (gtkut_widget_has_focus(compose->text) &&
10016 compose->gtkaspell &&
10017 compose->gtkaspell->check_while_typing)
10018 gtkaspell_highlight_all(compose->gtkaspell);
10022 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10024 Compose *compose = (Compose *)data;
10025 gint prev_autowrap;
10026 GtkTextBuffer *buffer;
10028 if (compose->focused_editable
10029 #ifndef GENERIC_UMPC
10030 && gtkut_widget_has_focus(compose->focused_editable)
10033 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10034 GDK_SELECTION_CLIPBOARD, NULL);
10038 if (gtkut_widget_has_focus(compose->text) &&
10039 compose->gtkaspell &&
10040 compose->gtkaspell->check_while_typing)
10041 gtkaspell_highlight_all(compose->gtkaspell);
10045 static void compose_allsel_cb(GtkAction *action, gpointer data)
10047 Compose *compose = (Compose *)data;
10048 if (compose->focused_editable
10049 #ifndef GENERIC_UMPC
10050 && gtkut_widget_has_focus(compose->focused_editable)
10053 entry_allsel(compose->focused_editable);
10056 static void textview_move_beginning_of_line (GtkTextView *text)
10058 GtkTextBuffer *buffer;
10062 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10064 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10065 mark = gtk_text_buffer_get_insert(buffer);
10066 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10067 gtk_text_iter_set_line_offset(&ins, 0);
10068 gtk_text_buffer_place_cursor(buffer, &ins);
10071 static void textview_move_forward_character (GtkTextView *text)
10073 GtkTextBuffer *buffer;
10077 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10079 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10080 mark = gtk_text_buffer_get_insert(buffer);
10081 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10082 if (gtk_text_iter_forward_cursor_position(&ins))
10083 gtk_text_buffer_place_cursor(buffer, &ins);
10086 static void textview_move_backward_character (GtkTextView *text)
10088 GtkTextBuffer *buffer;
10092 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10094 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10095 mark = gtk_text_buffer_get_insert(buffer);
10096 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10097 if (gtk_text_iter_backward_cursor_position(&ins))
10098 gtk_text_buffer_place_cursor(buffer, &ins);
10101 static void textview_move_forward_word (GtkTextView *text)
10103 GtkTextBuffer *buffer;
10108 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10110 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10111 mark = gtk_text_buffer_get_insert(buffer);
10112 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10113 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10114 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10115 gtk_text_iter_backward_word_start(&ins);
10116 gtk_text_buffer_place_cursor(buffer, &ins);
10120 static void textview_move_backward_word (GtkTextView *text)
10122 GtkTextBuffer *buffer;
10127 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10130 mark = gtk_text_buffer_get_insert(buffer);
10131 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10132 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10133 if (gtk_text_iter_backward_word_starts(&ins, 1))
10134 gtk_text_buffer_place_cursor(buffer, &ins);
10137 static void textview_move_end_of_line (GtkTextView *text)
10139 GtkTextBuffer *buffer;
10143 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10145 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10146 mark = gtk_text_buffer_get_insert(buffer);
10147 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10148 if (gtk_text_iter_forward_to_line_end(&ins))
10149 gtk_text_buffer_place_cursor(buffer, &ins);
10152 static void textview_move_next_line (GtkTextView *text)
10154 GtkTextBuffer *buffer;
10159 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10161 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10162 mark = gtk_text_buffer_get_insert(buffer);
10163 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10164 offset = gtk_text_iter_get_line_offset(&ins);
10165 if (gtk_text_iter_forward_line(&ins)) {
10166 gtk_text_iter_set_line_offset(&ins, offset);
10167 gtk_text_buffer_place_cursor(buffer, &ins);
10171 static void textview_move_previous_line (GtkTextView *text)
10173 GtkTextBuffer *buffer;
10178 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10181 mark = gtk_text_buffer_get_insert(buffer);
10182 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10183 offset = gtk_text_iter_get_line_offset(&ins);
10184 if (gtk_text_iter_backward_line(&ins)) {
10185 gtk_text_iter_set_line_offset(&ins, offset);
10186 gtk_text_buffer_place_cursor(buffer, &ins);
10190 static void textview_delete_forward_character (GtkTextView *text)
10192 GtkTextBuffer *buffer;
10194 GtkTextIter ins, end_iter;
10196 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10198 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10199 mark = gtk_text_buffer_get_insert(buffer);
10200 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10202 if (gtk_text_iter_forward_char(&end_iter)) {
10203 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10207 static void textview_delete_backward_character (GtkTextView *text)
10209 GtkTextBuffer *buffer;
10211 GtkTextIter ins, end_iter;
10213 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10215 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10216 mark = gtk_text_buffer_get_insert(buffer);
10217 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10219 if (gtk_text_iter_backward_char(&end_iter)) {
10220 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10224 static void textview_delete_forward_word (GtkTextView *text)
10226 GtkTextBuffer *buffer;
10228 GtkTextIter ins, end_iter;
10230 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10232 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10233 mark = gtk_text_buffer_get_insert(buffer);
10234 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10236 if (gtk_text_iter_forward_word_end(&end_iter)) {
10237 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10241 static void textview_delete_backward_word (GtkTextView *text)
10243 GtkTextBuffer *buffer;
10245 GtkTextIter ins, end_iter;
10247 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10249 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10250 mark = gtk_text_buffer_get_insert(buffer);
10251 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10253 if (gtk_text_iter_backward_word_start(&end_iter)) {
10254 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10258 static void textview_delete_line (GtkTextView *text)
10260 GtkTextBuffer *buffer;
10262 GtkTextIter ins, start_iter, end_iter;
10264 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10266 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10267 mark = gtk_text_buffer_get_insert(buffer);
10268 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10271 gtk_text_iter_set_line_offset(&start_iter, 0);
10274 if (gtk_text_iter_ends_line(&end_iter)){
10275 if (!gtk_text_iter_forward_char(&end_iter))
10276 gtk_text_iter_backward_char(&start_iter);
10279 gtk_text_iter_forward_to_line_end(&end_iter);
10280 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10283 static void textview_delete_to_line_end (GtkTextView *text)
10285 GtkTextBuffer *buffer;
10287 GtkTextIter ins, end_iter;
10289 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10291 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10292 mark = gtk_text_buffer_get_insert(buffer);
10293 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10295 if (gtk_text_iter_ends_line(&end_iter))
10296 gtk_text_iter_forward_char(&end_iter);
10298 gtk_text_iter_forward_to_line_end(&end_iter);
10299 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10302 #define DO_ACTION(name, act) { \
10303 if(!strcmp(name, a_name)) { \
10307 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10309 const gchar *a_name = gtk_action_get_name(action);
10310 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10311 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10312 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10313 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10314 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10315 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10316 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10317 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10318 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10319 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10320 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10321 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10322 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10323 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10327 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10329 Compose *compose = (Compose *)data;
10330 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10331 ComposeCallAdvancedAction action = -1;
10333 action = compose_call_advanced_action_from_path(gaction);
10336 void (*do_action) (GtkTextView *text);
10337 } action_table[] = {
10338 {textview_move_beginning_of_line},
10339 {textview_move_forward_character},
10340 {textview_move_backward_character},
10341 {textview_move_forward_word},
10342 {textview_move_backward_word},
10343 {textview_move_end_of_line},
10344 {textview_move_next_line},
10345 {textview_move_previous_line},
10346 {textview_delete_forward_character},
10347 {textview_delete_backward_character},
10348 {textview_delete_forward_word},
10349 {textview_delete_backward_word},
10350 {textview_delete_line},
10351 {textview_delete_to_line_end}
10354 if (!gtkut_widget_has_focus(GTK_WIDGET(text))) return;
10356 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10357 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10358 if (action_table[action].do_action)
10359 action_table[action].do_action(text);
10361 g_warning("Not implemented yet.");
10365 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10369 if (GTK_IS_EDITABLE(widget)) {
10370 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10371 gtk_editable_set_position(GTK_EDITABLE(widget),
10374 if (widget->parent && widget->parent->parent
10375 && widget->parent->parent->parent) {
10376 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10377 gint y = widget->allocation.y;
10378 gint height = widget->allocation.height;
10379 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10380 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10382 if (y < (int)shown->value) {
10383 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10385 if (y + height > (int)shown->value + (int)shown->page_size) {
10386 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10387 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10388 y + height - (int)shown->page_size - 1);
10390 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10391 (int)shown->upper - (int)shown->page_size - 1);
10398 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10399 compose->focused_editable = widget;
10401 #ifdef GENERIC_UMPC
10402 if (GTK_IS_TEXT_VIEW(widget)
10403 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10404 g_object_ref(compose->notebook);
10405 g_object_ref(compose->edit_vbox);
10406 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10407 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10408 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10409 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10410 g_object_unref(compose->notebook);
10411 g_object_unref(compose->edit_vbox);
10412 g_signal_handlers_block_by_func(G_OBJECT(widget),
10413 G_CALLBACK(compose_grab_focus_cb),
10415 gtk_widget_grab_focus(widget);
10416 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10417 G_CALLBACK(compose_grab_focus_cb),
10419 } else if (!GTK_IS_TEXT_VIEW(widget)
10420 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10421 g_object_ref(compose->notebook);
10422 g_object_ref(compose->edit_vbox);
10423 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10424 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10425 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10426 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10427 g_object_unref(compose->notebook);
10428 g_object_unref(compose->edit_vbox);
10429 g_signal_handlers_block_by_func(G_OBJECT(widget),
10430 G_CALLBACK(compose_grab_focus_cb),
10432 gtk_widget_grab_focus(widget);
10433 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10434 G_CALLBACK(compose_grab_focus_cb),
10440 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10442 compose->modified = TRUE;
10443 // compose_beautify_paragraph(compose, NULL, TRUE);
10444 #ifndef GENERIC_UMPC
10445 compose_set_title(compose);
10449 static void compose_wrap_cb(GtkAction *action, gpointer data)
10451 Compose *compose = (Compose *)data;
10452 compose_beautify_paragraph(compose, NULL, TRUE);
10455 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10457 Compose *compose = (Compose *)data;
10458 compose_wrap_all_full(compose, TRUE);
10461 static void compose_find_cb(GtkAction *action, gpointer data)
10463 Compose *compose = (Compose *)data;
10465 message_search_compose(compose);
10468 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10471 Compose *compose = (Compose *)data;
10472 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10473 if (compose->autowrap)
10474 compose_wrap_all_full(compose, TRUE);
10475 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10478 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10481 Compose *compose = (Compose *)data;
10482 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10485 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10487 Compose *compose = (Compose *)data;
10489 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10492 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10494 Compose *compose = (Compose *)data;
10496 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10499 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10501 g_free(compose->privacy_system);
10503 compose->privacy_system = g_strdup(account->default_privacy_system);
10504 compose_update_privacy_system_menu_item(compose, warn);
10507 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10509 Compose *compose = (Compose *)data;
10511 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10512 gtk_widget_show(compose->ruler_hbox);
10513 prefs_common.show_ruler = TRUE;
10515 gtk_widget_hide(compose->ruler_hbox);
10516 gtk_widget_queue_resize(compose->edit_vbox);
10517 prefs_common.show_ruler = FALSE;
10521 static void compose_attach_drag_received_cb (GtkWidget *widget,
10522 GdkDragContext *context,
10525 GtkSelectionData *data,
10528 gpointer user_data)
10530 Compose *compose = (Compose *)user_data;
10533 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10535 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10537 ) && gtk_drag_get_source_widget(context) !=
10538 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10539 list = uri_list_extract_filenames((const gchar *)data->data);
10540 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10541 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10542 compose_attach_append
10543 (compose, (const gchar *)tmp->data,
10544 utf8_filename, NULL);
10545 g_free(utf8_filename);
10547 if (list) compose_changed_cb(NULL, compose);
10548 list_free_strings(list);
10550 } else if (gtk_drag_get_source_widget(context)
10551 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10552 /* comes from our summaryview */
10553 SummaryView * summaryview = NULL;
10554 GSList * list = NULL, *cur = NULL;
10556 if (mainwindow_get_mainwindow())
10557 summaryview = mainwindow_get_mainwindow()->summaryview;
10560 list = summary_get_selected_msg_list(summaryview);
10562 for (cur = list; cur; cur = cur->next) {
10563 MsgInfo *msginfo = (MsgInfo *)cur->data;
10564 gchar *file = NULL;
10566 file = procmsg_get_message_file_full(msginfo,
10569 compose_attach_append(compose, (const gchar *)file,
10570 (const gchar *)file, "message/rfc822");
10574 g_slist_free(list);
10578 static gboolean compose_drag_drop(GtkWidget *widget,
10579 GdkDragContext *drag_context,
10581 guint time, gpointer user_data)
10583 /* not handling this signal makes compose_insert_drag_received_cb
10588 static void compose_insert_drag_received_cb (GtkWidget *widget,
10589 GdkDragContext *drag_context,
10592 GtkSelectionData *data,
10595 gpointer user_data)
10597 Compose *compose = (Compose *)user_data;
10600 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10603 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10605 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10607 AlertValue val = G_ALERTDEFAULT;
10609 list = uri_list_extract_filenames((const gchar *)data->data);
10610 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10611 /* Assume a list of no files, and data has ://, is a remote link */
10612 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10613 gchar *tmpfile = get_tmp_file();
10614 str_write_to_file(tmpdata, tmpfile);
10616 compose_insert_file(compose, tmpfile);
10617 claws_unlink(tmpfile);
10619 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10620 compose_beautify_paragraph(compose, NULL, TRUE);
10623 switch (prefs_common.compose_dnd_mode) {
10624 case COMPOSE_DND_ASK:
10625 val = alertpanel_full(_("Insert or attach?"),
10626 _("Do you want to insert the contents of the file(s) "
10627 "into the message body, or attach it to the email?"),
10628 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10629 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10631 case COMPOSE_DND_INSERT:
10632 val = G_ALERTALTERNATE;
10634 case COMPOSE_DND_ATTACH:
10635 val = G_ALERTOTHER;
10638 /* unexpected case */
10639 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10642 if (val & G_ALERTDISABLE) {
10643 val &= ~G_ALERTDISABLE;
10644 /* remember what action to perform by default, only if we don't click Cancel */
10645 if (val == G_ALERTALTERNATE)
10646 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10647 else if (val == G_ALERTOTHER)
10648 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10651 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10652 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10653 list_free_strings(list);
10656 } else if (val == G_ALERTOTHER) {
10657 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10658 list_free_strings(list);
10663 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10664 compose_insert_file(compose, (const gchar *)tmp->data);
10666 list_free_strings(list);
10668 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10673 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10676 static void compose_header_drag_received_cb (GtkWidget *widget,
10677 GdkDragContext *drag_context,
10680 GtkSelectionData *data,
10683 gpointer user_data)
10685 GtkEditable *entry = (GtkEditable *)user_data;
10686 gchar *email = (gchar *)data->data;
10688 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10691 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10692 gchar *decoded=g_new(gchar, strlen(email));
10695 email += strlen("mailto:");
10696 decode_uri(decoded, email); /* will fit */
10697 gtk_editable_delete_text(entry, 0, -1);
10698 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10699 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10703 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10706 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10708 Compose *compose = (Compose *)data;
10710 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10711 compose->return_receipt = TRUE;
10713 compose->return_receipt = FALSE;
10716 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10718 Compose *compose = (Compose *)data;
10720 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10721 compose->remove_references = TRUE;
10723 compose->remove_references = FALSE;
10726 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10727 ComposeHeaderEntry *headerentry)
10729 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10733 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10734 GdkEventKey *event,
10735 ComposeHeaderEntry *headerentry)
10737 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10738 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10739 !(event->state & GDK_MODIFIER_MASK) &&
10740 (event->keyval == GDK_BackSpace) &&
10741 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10742 gtk_container_remove
10743 (GTK_CONTAINER(headerentry->compose->header_table),
10744 headerentry->combo);
10745 gtk_container_remove
10746 (GTK_CONTAINER(headerentry->compose->header_table),
10747 headerentry->entry);
10748 headerentry->compose->header_list =
10749 g_slist_remove(headerentry->compose->header_list,
10751 g_free(headerentry);
10752 } else if (event->keyval == GDK_Tab) {
10753 if (headerentry->compose->header_last == headerentry) {
10754 /* Override default next focus, and give it to subject_entry
10755 * instead of notebook tabs
10757 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10758 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10765 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10766 ComposeHeaderEntry *headerentry)
10768 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10769 compose_create_header_entry(headerentry->compose);
10770 g_signal_handlers_disconnect_matched
10771 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10772 0, 0, NULL, NULL, headerentry);
10774 /* Automatically scroll down */
10775 GTK_EVENTS_FLUSH();
10776 compose_show_first_last_header(headerentry->compose, FALSE);
10782 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10784 GtkAdjustment *vadj;
10786 cm_return_if_fail(compose);
10787 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10788 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10789 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10790 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10791 gtk_adjustment_changed(vadj);
10794 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10795 const gchar *text, gint len, Compose *compose)
10797 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10798 (G_OBJECT(compose->text), "paste_as_quotation"));
10801 cm_return_if_fail(text != NULL);
10803 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10804 G_CALLBACK(text_inserted),
10806 if (paste_as_quotation) {
10808 const gchar *qmark;
10810 GtkTextIter start_iter;
10813 len = strlen(text);
10815 new_text = g_strndup(text, len);
10817 qmark = compose_quote_char_from_context(compose);
10819 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10820 gtk_text_buffer_place_cursor(buffer, iter);
10822 pos = gtk_text_iter_get_offset(iter);
10824 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10825 _("Quote format error at line %d."));
10826 quote_fmt_reset_vartable();
10828 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10829 GINT_TO_POINTER(paste_as_quotation - 1));
10831 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10832 gtk_text_buffer_place_cursor(buffer, iter);
10833 gtk_text_buffer_delete_mark(buffer, mark);
10835 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10836 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10837 compose_beautify_paragraph(compose, &start_iter, FALSE);
10838 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10839 gtk_text_buffer_delete_mark(buffer, mark);
10841 if (strcmp(text, "\n") || compose->automatic_break
10842 || gtk_text_iter_starts_line(iter)) {
10843 GtkTextIter before_ins;
10844 gtk_text_buffer_insert(buffer, iter, text, len);
10845 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10846 before_ins = *iter;
10847 gtk_text_iter_backward_chars(&before_ins, len);
10848 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10851 /* check if the preceding is just whitespace or quote */
10852 GtkTextIter start_line;
10853 gchar *tmp = NULL, *quote = NULL;
10854 gint quote_len = 0, is_normal = 0;
10855 start_line = *iter;
10856 gtk_text_iter_set_line_offset(&start_line, 0);
10857 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10860 if (*tmp == '\0') {
10863 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10871 gtk_text_buffer_insert(buffer, iter, text, len);
10873 gtk_text_buffer_insert_with_tags_by_name(buffer,
10874 iter, text, len, "no_join", NULL);
10879 if (!paste_as_quotation) {
10880 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10881 compose_beautify_paragraph(compose, iter, FALSE);
10882 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10883 gtk_text_buffer_delete_mark(buffer, mark);
10886 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10887 G_CALLBACK(text_inserted),
10889 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10891 if (prefs_common.autosave &&
10892 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10893 compose->draft_timeout_tag != -2 /* disabled while loading */)
10894 compose->draft_timeout_tag = g_timeout_add
10895 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10897 static gint compose_defer_auto_save_draft(Compose *compose)
10899 compose->draft_timeout_tag = -1;
10900 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10905 static void compose_check_all(GtkAction *action, gpointer data)
10907 Compose *compose = (Compose *)data;
10908 if (!compose->gtkaspell)
10911 if (gtkut_widget_has_focus(compose->subject_entry))
10912 claws_spell_entry_check_all(
10913 CLAWS_SPELL_ENTRY(compose->subject_entry));
10915 gtkaspell_check_all(compose->gtkaspell);
10918 static void compose_highlight_all(GtkAction *action, gpointer data)
10920 Compose *compose = (Compose *)data;
10921 if (compose->gtkaspell) {
10922 claws_spell_entry_recheck_all(
10923 CLAWS_SPELL_ENTRY(compose->subject_entry));
10924 gtkaspell_highlight_all(compose->gtkaspell);
10928 static void compose_check_backwards(GtkAction *action, gpointer data)
10930 Compose *compose = (Compose *)data;
10931 if (!compose->gtkaspell) {
10932 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10936 if (gtkut_widget_has_focus(compose->subject_entry))
10937 claws_spell_entry_check_backwards(
10938 CLAWS_SPELL_ENTRY(compose->subject_entry));
10940 gtkaspell_check_backwards(compose->gtkaspell);
10943 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10945 Compose *compose = (Compose *)data;
10946 if (!compose->gtkaspell) {
10947 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10951 if (gtkut_widget_has_focus(compose->subject_entry))
10952 claws_spell_entry_check_forwards_go(
10953 CLAWS_SPELL_ENTRY(compose->subject_entry));
10955 gtkaspell_check_forwards_go(compose->gtkaspell);
10960 *\brief Guess originating forward account from MsgInfo and several
10961 * "common preference" settings. Return NULL if no guess.
10963 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10965 PrefsAccount *account = NULL;
10967 cm_return_val_if_fail(msginfo, NULL);
10968 cm_return_val_if_fail(msginfo->folder, NULL);
10969 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10971 if (msginfo->folder->prefs->enable_default_account)
10972 account = account_find_from_id(msginfo->folder->prefs->default_account);
10975 account = msginfo->folder->folder->account;
10977 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10979 Xstrdup_a(to, msginfo->to, return NULL);
10980 extract_address(to);
10981 account = account_find_from_address(to, FALSE);
10984 if (!account && prefs_common.forward_account_autosel) {
10985 gchar cc[BUFFSIZE];
10986 if (!procheader_get_header_from_msginfo
10987 (msginfo, cc,sizeof cc , "Cc:")) {
10988 gchar *buf = cc + strlen("Cc:");
10989 extract_address(buf);
10990 account = account_find_from_address(buf, FALSE);
10994 if (!account && prefs_common.forward_account_autosel) {
10995 gchar deliveredto[BUFFSIZE];
10996 if (!procheader_get_header_from_msginfo
10997 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10998 gchar *buf = deliveredto + strlen("Delivered-To:");
10999 extract_address(buf);
11000 account = account_find_from_address(buf, FALSE);
11007 gboolean compose_close(Compose *compose)
11011 if (!g_mutex_trylock(compose->mutex)) {
11012 /* we have to wait for the (possibly deferred by auto-save)
11013 * drafting to be done, before destroying the compose under
11015 debug_print("waiting for drafting to finish...\n");
11016 compose_allow_user_actions(compose, FALSE);
11017 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11020 cm_return_val_if_fail(compose, FALSE);
11021 gtkut_widget_get_uposition(compose->window, &x, &y);
11022 if (!compose->batch) {
11023 prefs_common.compose_x = x;
11024 prefs_common.compose_y = y;
11026 g_mutex_unlock(compose->mutex);
11027 compose_destroy(compose);
11032 * Add entry field for each address in list.
11033 * \param compose E-Mail composition object.
11034 * \param listAddress List of (formatted) E-Mail addresses.
11036 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11039 node = listAddress;
11041 addr = ( gchar * ) node->data;
11042 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11043 node = g_list_next( node );
11047 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11048 guint action, gboolean opening_multiple)
11050 gchar *body = NULL;
11051 GSList *new_msglist = NULL;
11052 MsgInfo *tmp_msginfo = NULL;
11053 gboolean originally_enc = FALSE;
11054 gboolean originally_sig = FALSE;
11055 Compose *compose = NULL;
11056 gchar *s_system = NULL;
11058 cm_return_if_fail(msgview != NULL);
11060 cm_return_if_fail(msginfo_list != NULL);
11062 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11063 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11064 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11066 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11067 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11068 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11069 orig_msginfo, mimeinfo);
11070 if (tmp_msginfo != NULL) {
11071 new_msglist = g_slist_append(NULL, tmp_msginfo);
11073 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11074 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11075 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11077 tmp_msginfo->folder = orig_msginfo->folder;
11078 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11079 if (orig_msginfo->tags) {
11080 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11081 tmp_msginfo->folder->tags_dirty = TRUE;
11087 if (!opening_multiple)
11088 body = messageview_get_selection(msgview);
11091 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11092 procmsg_msginfo_free(tmp_msginfo);
11093 g_slist_free(new_msglist);
11095 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11097 if (compose && originally_enc) {
11098 compose_force_encryption(compose, compose->account, FALSE, s_system);
11101 if (compose && originally_sig && compose->account->default_sign_reply) {
11102 compose_force_signing(compose, compose->account, s_system);
11106 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11109 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11112 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11113 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11114 GSList *cur = msginfo_list;
11115 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11116 "messages. Opening the windows "
11117 "could take some time. Do you "
11118 "want to continue?"),
11119 g_slist_length(msginfo_list));
11120 if (g_slist_length(msginfo_list) > 9
11121 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11122 != G_ALERTALTERNATE) {
11127 /* We'll open multiple compose windows */
11128 /* let the WM place the next windows */
11129 compose_force_window_origin = FALSE;
11130 for (; cur; cur = cur->next) {
11132 tmplist.data = cur->data;
11133 tmplist.next = NULL;
11134 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11136 compose_force_window_origin = TRUE;
11138 /* forwarding multiple mails as attachments is done via a
11139 * single compose window */
11140 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11144 void compose_check_for_email_account(Compose *compose)
11146 PrefsAccount *ac = NULL, *curr = NULL;
11152 if (compose->account && compose->account->protocol == A_NNTP) {
11153 ac = account_get_cur_account();
11154 if (ac->protocol == A_NNTP) {
11155 list = account_get_list();
11157 for( ; list != NULL ; list = g_list_next(list)) {
11158 curr = (PrefsAccount *) list->data;
11159 if (curr->protocol != A_NNTP) {
11165 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11170 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11171 const gchar *address)
11173 GSList *msginfo_list = NULL;
11174 gchar *body = messageview_get_selection(msgview);
11177 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11179 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11180 compose_check_for_email_account(compose);
11181 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11182 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11183 compose_reply_set_subject(compose, msginfo);
11186 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11189 void compose_set_position(Compose *compose, gint pos)
11191 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11193 gtkut_text_view_set_position(text, pos);
11196 gboolean compose_search_string(Compose *compose,
11197 const gchar *str, gboolean case_sens)
11199 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11201 return gtkut_text_view_search_string(text, str, case_sens);
11204 gboolean compose_search_string_backward(Compose *compose,
11205 const gchar *str, gboolean case_sens)
11207 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11209 return gtkut_text_view_search_string_backward(text, str, case_sens);
11212 /* allocate a msginfo structure and populate its data from a compose data structure */
11213 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11215 MsgInfo *newmsginfo;
11217 gchar buf[BUFFSIZE];
11219 cm_return_val_if_fail( compose != NULL, NULL );
11221 newmsginfo = procmsg_msginfo_new();
11224 get_rfc822_date(buf, sizeof(buf));
11225 newmsginfo->date = g_strdup(buf);
11228 if (compose->from_name) {
11229 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11230 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11234 if (compose->subject_entry)
11235 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11237 /* to, cc, reply-to, newsgroups */
11238 for (list = compose->header_list; list; list = list->next) {
11239 gchar *header = gtk_editable_get_chars(
11241 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11242 gchar *entry = gtk_editable_get_chars(
11243 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11245 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11246 if ( newmsginfo->to == NULL ) {
11247 newmsginfo->to = g_strdup(entry);
11248 } else if (entry && *entry) {
11249 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11250 g_free(newmsginfo->to);
11251 newmsginfo->to = tmp;
11254 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11255 if ( newmsginfo->cc == NULL ) {
11256 newmsginfo->cc = g_strdup(entry);
11257 } else if (entry && *entry) {
11258 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11259 g_free(newmsginfo->cc);
11260 newmsginfo->cc = tmp;
11263 if ( strcasecmp(header,
11264 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11265 if ( newmsginfo->newsgroups == NULL ) {
11266 newmsginfo->newsgroups = g_strdup(entry);
11267 } else if (entry && *entry) {
11268 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11269 g_free(newmsginfo->newsgroups);
11270 newmsginfo->newsgroups = tmp;
11278 /* other data is unset */
11284 /* update compose's dictionaries from folder dict settings */
11285 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11286 FolderItem *folder_item)
11288 cm_return_if_fail(compose != NULL);
11290 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11291 FolderItemPrefs *prefs = folder_item->prefs;
11293 if (prefs->enable_default_dictionary)
11294 gtkaspell_change_dict(compose->gtkaspell,
11295 prefs->default_dictionary, FALSE);
11296 if (folder_item->prefs->enable_default_alt_dictionary)
11297 gtkaspell_change_alt_dict(compose->gtkaspell,
11298 prefs->default_alt_dictionary);
11299 if (prefs->enable_default_dictionary
11300 || prefs->enable_default_alt_dictionary)
11301 compose_spell_menu_changed(compose);