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