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);
537 static GtkActionEntry compose_popup_entries[] =
539 {"Compose", NULL, "Compose" },
540 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
541 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
542 {"Compose/---", NULL, "---", NULL, NULL, NULL },
543 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
546 static GtkActionEntry compose_entries[] =
548 {"Menu", NULL, "Menu" },
550 {"Message", NULL, N_("_Message") },
551 {"Edit", NULL, N_("_Edit") },
553 {"Spelling", NULL, N_("_Spelling") },
555 {"Options", NULL, N_("_Options") },
556 {"Tools", NULL, N_("_Tools") },
557 {"Help", NULL, N_("_Help") },
559 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
560 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
561 {"Message/---", NULL, "---" },
563 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
564 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
565 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
566 /* {"Message/---", NULL, "---" }, */
567 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
568 /* {"Message/---", NULL, "---" }, */
569 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
572 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
573 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
574 {"Edit/---", NULL, "---" },
576 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
577 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
578 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
580 {"Edit/SpecialPaste", NULL, N_("Special paste") },
581 {"Edit/SpecialPaste/AsQuotation", NULL, N_("as _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
582 {"Edit/SpecialPaste/Wrapped", NULL, N_("_wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
583 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
585 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
587 {"Edit/Advanced", NULL, N_("A_dvanced") },
588 {"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*/
589 {"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*/
590 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
591 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
592 {"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*/
593 {"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*/
594 {"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*/
595 {"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*/
596 {"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*/
597 {"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*/
598 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
599 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
600 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
601 {"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*/
603 /* {"Edit/---", NULL, "---" }, */
604 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
606 /* {"Edit/---", NULL, "---" }, */
607 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
608 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
609 /* {"Edit/---", NULL, "---" }, */
610 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
613 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
614 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
615 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
616 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
618 {"Spelling/---", NULL, "---" },
619 {"Spelling/Options", NULL, N_("_Options") },
624 {"Options/ReplyMode", NULL, N_("Reply _mode") },
625 {"Options/---", NULL, "---" },
626 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
627 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
629 /* {"Options/---", NULL, "---" }, */
631 {"Options/Priority", NULL, N_("_Priority") },
633 {"Options/Encoding", NULL, N_("Character _encoding") },
634 {"Options/Encoding/---", NULL, "---" },
635 #define ENC_ACTION(cs_char,c_char,string) \
636 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
638 {"Options/Encoding/Western", NULL, N_("Western European") },
639 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
640 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
641 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
642 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
643 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
644 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
645 {"Options/Encoding/Korean", NULL, N_("Korean") },
646 {"Options/Encoding/Thai", NULL, N_("Thai") },
649 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
651 {"Tools/Template", NULL, N_("_Template") },
652 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
653 {"Tools/Actions", NULL, N_("Actio_ns") },
654 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
657 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
660 static GtkToggleActionEntry compose_toggle_entries[] =
662 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
663 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
664 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
665 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
666 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
667 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
668 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
671 static GtkRadioActionEntry compose_radio_rm_entries[] =
673 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
674 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
675 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
676 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
679 static GtkRadioActionEntry compose_radio_prio_entries[] =
681 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
682 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
683 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
684 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
685 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
688 static GtkRadioActionEntry compose_radio_enc_entries[] =
690 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
691 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
692 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
693 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
694 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
695 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
696 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
697 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
698 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
699 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
700 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
701 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
702 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
703 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
704 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
705 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
706 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
707 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
708 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
709 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
710 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
711 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
712 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
713 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
714 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
715 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
716 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
724 static GtkTargetEntry compose_mime_types[] =
726 {"text/uri-list", 0, 0},
727 {"UTF8_STRING", 0, 0},
731 static gboolean compose_put_existing_to_front(MsgInfo *info)
733 GList *compose_list = compose_get_compose_list();
737 for (elem = compose_list; elem != NULL && elem->data != NULL;
739 Compose *c = (Compose*)elem->data;
741 if (!c->targetinfo || !c->targetinfo->msgid ||
745 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
746 gtkut_window_popup(c->window);
754 static GdkColor quote_color1 =
755 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
756 static GdkColor quote_color2 =
757 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
758 static GdkColor quote_color3 =
759 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
761 static GdkColor quote_bgcolor1 =
762 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
763 static GdkColor quote_bgcolor2 =
764 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
765 static GdkColor quote_bgcolor3 =
766 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
768 static GdkColor signature_color = {
775 static GdkColor uri_color = {
782 static void compose_create_tags(GtkTextView *text, Compose *compose)
784 GtkTextBuffer *buffer;
785 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 buffer = gtk_text_view_get_buffer(text);
793 if (prefs_common.enable_color) {
794 /* grab the quote colors, converting from an int to a GdkColor */
795 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
797 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
799 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
801 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
803 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
805 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
807 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
809 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
812 signature_color = quote_color1 = quote_color2 = quote_color3 =
813 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
816 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
817 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
818 "foreground-gdk", "e_color1,
819 "paragraph-background-gdk", "e_bgcolor1,
821 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
822 "foreground-gdk", "e_color2,
823 "paragraph-background-gdk", "e_bgcolor2,
825 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
826 "foreground-gdk", "e_color3,
827 "paragraph-background-gdk", "e_bgcolor3,
830 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
831 "foreground-gdk", "e_color1,
833 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
834 "foreground-gdk", "e_color2,
836 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
837 "foreground-gdk", "e_color3,
841 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
842 "foreground-gdk", &signature_color,
845 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
846 "foreground-gdk", &uri_color,
848 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
849 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
851 color[0] = quote_color1;
852 color[1] = quote_color2;
853 color[2] = quote_color3;
854 color[3] = quote_bgcolor1;
855 color[4] = quote_bgcolor2;
856 color[5] = quote_bgcolor3;
857 color[6] = signature_color;
858 color[7] = uri_color;
859 cmap = gdk_drawable_get_colormap(compose->window->window);
860 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
862 for (i = 0; i < 8; i++) {
863 if (success[i] == FALSE) {
866 g_warning("Compose: color allocation failed.\n");
867 style = gtk_widget_get_style(GTK_WIDGET(text));
868 quote_color1 = quote_color2 = quote_color3 =
869 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
870 signature_color = uri_color = black;
875 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
876 GPtrArray *attach_files)
878 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
881 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
883 return compose_generic_new(account, mailto, item, NULL, NULL);
886 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
888 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
891 #define SCROLL_TO_CURSOR(compose) { \
892 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
893 gtk_text_view_get_buffer( \
894 GTK_TEXT_VIEW(compose->text))); \
895 gtk_text_view_scroll_mark_onscreen( \
896 GTK_TEXT_VIEW(compose->text), \
900 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
903 if (folderidentifier) {
904 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
905 prefs_common.compose_save_to_history = add_history(
906 prefs_common.compose_save_to_history, folderidentifier);
907 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
908 prefs_common.compose_save_to_history);
911 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
912 if (folderidentifier)
913 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
915 gtk_entry_set_text(GTK_ENTRY(entry), "");
918 static gchar *compose_get_save_to(Compose *compose)
921 gchar *result = NULL;
922 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
923 result = gtk_editable_get_chars(entry, 0, -1);
926 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
927 prefs_common.compose_save_to_history = add_history(
928 prefs_common.compose_save_to_history, result);
929 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
930 prefs_common.compose_save_to_history);
935 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
936 GPtrArray *attach_files, GList *listAddress )
939 GtkTextView *textview;
940 GtkTextBuffer *textbuf;
942 const gchar *subject_format = NULL;
943 const gchar *body_format = NULL;
944 gchar *mailto_from = NULL;
945 PrefsAccount *mailto_account = NULL;
946 MsgInfo* dummyinfo = NULL;
947 MailField mfield = NO_FIELD_PRESENT;
951 /* check if mailto defines a from */
952 if (mailto && *mailto != '\0') {
953 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
954 /* mailto defines a from, check if we can get account prefs from it,
955 if not, the account prefs will be guessed using other ways, but we'll keep
958 mailto_account = account_find_from_address(mailto_from, TRUE);
960 account = mailto_account;
963 /* if no account prefs set from mailto, set if from folder prefs (if any) */
964 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
965 account = account_find_from_id(item->prefs->default_account);
967 /* if no account prefs set, fallback to the current one */
968 if (!account) account = cur_account;
969 cm_return_val_if_fail(account != NULL, NULL);
971 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
973 /* override from name if mailto asked for it */
975 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
978 /* override from name according to folder properties */
979 if (item && item->prefs &&
980 item->prefs->compose_with_format &&
981 item->prefs->compose_override_from_format &&
982 *item->prefs->compose_override_from_format != '\0') {
987 dummyinfo = compose_msginfo_new_from_compose(compose);
989 /* decode \-escape sequences in the internal representation of the quote format */
990 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
991 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
994 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
997 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
999 quote_fmt_scan_string(tmp);
1002 buf = quote_fmt_get_buffer();
1004 alertpanel_error(_("New message From format error."));
1006 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1007 quote_fmt_reset_vartable();
1012 compose->replyinfo = NULL;
1013 compose->fwdinfo = NULL;
1015 textview = GTK_TEXT_VIEW(compose->text);
1016 textbuf = gtk_text_view_get_buffer(textview);
1017 compose_create_tags(textview, compose);
1019 undo_block(compose->undostruct);
1021 compose_set_dictionaries_from_folder_prefs(compose, item);
1024 if (account->auto_sig)
1025 compose_insert_sig(compose, FALSE);
1026 gtk_text_buffer_get_start_iter(textbuf, &iter);
1027 gtk_text_buffer_place_cursor(textbuf, &iter);
1029 if (account->protocol != A_NNTP) {
1030 if (mailto && *mailto != '\0') {
1031 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1033 } else if (item && item->prefs) {
1034 if (item->prefs->enable_default_bcc) {
1035 compose_entry_append(compose, item->prefs->default_bcc,
1036 COMPOSE_BCC, PREF_FOLDER);
1038 if (item->prefs->enable_default_cc) {
1039 compose_entry_append(compose, item->prefs->default_cc,
1040 COMPOSE_CC, PREF_FOLDER);
1042 if (item->prefs->enable_default_replyto) {
1043 compose_entry_append(compose, item->prefs->default_replyto,
1044 COMPOSE_REPLYTO, PREF_FOLDER);
1046 if (item->prefs->enable_default_to) {
1047 compose_entry_append(compose, item->prefs->default_to,
1048 COMPOSE_TO, PREF_FOLDER);
1049 compose_entry_mark_default_to(compose, item->prefs->default_to);
1052 if (item && item->ret_rcpt) {
1053 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1056 if (mailto && *mailto != '\0') {
1057 if (!strchr(mailto, '@'))
1058 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1060 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1061 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1062 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1063 mfield = TO_FIELD_PRESENT;
1066 * CLAWS: just don't allow return receipt request, even if the user
1067 * may want to send an email. simple but foolproof.
1069 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1071 compose_add_field_list( compose, listAddress );
1073 if (item && item->prefs && item->prefs->compose_with_format) {
1074 subject_format = item->prefs->compose_subject_format;
1075 body_format = item->prefs->compose_body_format;
1076 } else if (account->compose_with_format) {
1077 subject_format = account->compose_subject_format;
1078 body_format = account->compose_body_format;
1079 } else if (prefs_common.compose_with_format) {
1080 subject_format = prefs_common.compose_subject_format;
1081 body_format = prefs_common.compose_body_format;
1084 if (subject_format || body_format) {
1087 && *subject_format != '\0' )
1089 gchar *subject = NULL;
1094 dummyinfo = compose_msginfo_new_from_compose(compose);
1096 /* decode \-escape sequences in the internal representation of the quote format */
1097 tmp = malloc(strlen(subject_format)+1);
1098 pref_get_unescaped_pref(tmp, subject_format);
1100 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1102 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1103 compose->gtkaspell);
1105 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1107 quote_fmt_scan_string(tmp);
1110 buf = quote_fmt_get_buffer();
1112 alertpanel_error(_("New message subject format error."));
1114 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1115 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1116 quote_fmt_reset_vartable();
1120 mfield = SUBJECT_FIELD_PRESENT;
1124 && *body_format != '\0' )
1127 GtkTextBuffer *buffer;
1128 GtkTextIter start, end;
1132 dummyinfo = compose_msginfo_new_from_compose(compose);
1134 text = GTK_TEXT_VIEW(compose->text);
1135 buffer = gtk_text_view_get_buffer(text);
1136 gtk_text_buffer_get_start_iter(buffer, &start);
1137 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1138 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1140 compose_quote_fmt(compose, dummyinfo,
1142 NULL, tmp, FALSE, TRUE,
1143 _("The body of the \"New message\" template has an error at line %d."));
1144 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1145 quote_fmt_reset_vartable();
1149 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1150 gtkaspell_highlight_all(compose->gtkaspell);
1152 mfield = BODY_FIELD_PRESENT;
1156 procmsg_msginfo_free( dummyinfo );
1162 for (i = 0; i < attach_files->len; i++) {
1163 file = g_ptr_array_index(attach_files, i);
1164 compose_attach_append(compose, file, file, NULL);
1168 compose_show_first_last_header(compose, TRUE);
1170 /* Set save folder */
1171 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1172 gchar *folderidentifier;
1174 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1175 folderidentifier = folder_item_get_identifier(item);
1176 compose_set_save_to(compose, folderidentifier);
1177 g_free(folderidentifier);
1180 /* Place cursor according to provided input (mfield) */
1182 case NO_FIELD_PRESENT:
1183 if (compose->header_last)
1184 gtk_widget_grab_focus(compose->header_last->entry);
1186 case TO_FIELD_PRESENT:
1187 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1189 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1192 gtk_widget_grab_focus(compose->subject_entry);
1194 case SUBJECT_FIELD_PRESENT:
1195 textview = GTK_TEXT_VIEW(compose->text);
1198 textbuf = gtk_text_view_get_buffer(textview);
1201 mark = gtk_text_buffer_get_insert(textbuf);
1202 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1203 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1205 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1206 * only defers where it comes to the variable body
1207 * is not null. If no body is present compose->text
1208 * will be null in which case you cannot place the
1209 * cursor inside the component so. An empty component
1210 * is therefore created before placing the cursor
1212 case BODY_FIELD_PRESENT:
1213 gtk_widget_grab_focus(compose->text);
1217 undo_unblock(compose->undostruct);
1219 if (prefs_common.auto_exteditor)
1220 compose_exec_ext_editor(compose);
1222 compose->draft_timeout_tag = -1;
1223 SCROLL_TO_CURSOR(compose);
1225 compose->modified = FALSE;
1226 compose_set_title(compose);
1228 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1233 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1234 gboolean override_pref, const gchar *system)
1236 const gchar *privacy = NULL;
1238 cm_return_if_fail(compose != NULL);
1239 cm_return_if_fail(account != NULL);
1241 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1246 else if (account->default_privacy_system
1247 && strlen(account->default_privacy_system)) {
1248 privacy = account->default_privacy_system;
1250 GSList *privacy_avail = privacy_get_system_ids();
1251 if (privacy_avail && g_slist_length(privacy_avail)) {
1252 privacy = (gchar *)(privacy_avail->data);
1255 if (privacy != NULL) {
1257 g_free(compose->privacy_system);
1258 compose->privacy_system = NULL;
1260 if (compose->privacy_system == NULL)
1261 compose->privacy_system = g_strdup(privacy);
1262 else if (*(compose->privacy_system) == '\0') {
1263 g_free(compose->privacy_system);
1264 compose->privacy_system = g_strdup(privacy);
1266 compose_update_privacy_system_menu_item(compose, FALSE);
1267 compose_use_encryption(compose, TRUE);
1271 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1273 const gchar *privacy = NULL;
1277 else if (account->default_privacy_system
1278 && strlen(account->default_privacy_system)) {
1279 privacy = account->default_privacy_system;
1281 GSList *privacy_avail = privacy_get_system_ids();
1282 if (privacy_avail && g_slist_length(privacy_avail)) {
1283 privacy = (gchar *)(privacy_avail->data);
1287 if (privacy != NULL) {
1289 g_free(compose->privacy_system);
1290 compose->privacy_system = NULL;
1292 if (compose->privacy_system == NULL)
1293 compose->privacy_system = g_strdup(privacy);
1294 compose_update_privacy_system_menu_item(compose, FALSE);
1295 compose_use_signing(compose, TRUE);
1299 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1303 Compose *compose = NULL;
1305 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1307 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1308 cm_return_val_if_fail(msginfo != NULL, NULL);
1310 list_len = g_slist_length(msginfo_list);
1314 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1315 FALSE, prefs_common.default_reply_list, FALSE, body);
1317 case COMPOSE_REPLY_WITH_QUOTE:
1318 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1319 FALSE, prefs_common.default_reply_list, FALSE, body);
1321 case COMPOSE_REPLY_WITHOUT_QUOTE:
1322 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1323 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1325 case COMPOSE_REPLY_TO_SENDER:
1326 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1327 FALSE, FALSE, TRUE, body);
1329 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1330 compose = compose_followup_and_reply_to(msginfo,
1331 COMPOSE_QUOTE_CHECK,
1332 FALSE, FALSE, body);
1334 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1335 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1336 FALSE, FALSE, TRUE, body);
1338 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1339 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1340 FALSE, FALSE, TRUE, NULL);
1342 case COMPOSE_REPLY_TO_ALL:
1343 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1344 TRUE, FALSE, FALSE, body);
1346 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1347 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1348 TRUE, FALSE, FALSE, body);
1350 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1351 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1352 TRUE, FALSE, FALSE, NULL);
1354 case COMPOSE_REPLY_TO_LIST:
1355 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1356 FALSE, TRUE, FALSE, body);
1358 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1359 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1360 FALSE, TRUE, FALSE, body);
1362 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1363 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1364 FALSE, TRUE, FALSE, NULL);
1366 case COMPOSE_FORWARD:
1367 if (prefs_common.forward_as_attachment) {
1368 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1371 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1375 case COMPOSE_FORWARD_INLINE:
1376 /* check if we reply to more than one Message */
1377 if (list_len == 1) {
1378 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1381 /* more messages FALL THROUGH */
1382 case COMPOSE_FORWARD_AS_ATTACH:
1383 compose = compose_forward_multiple(NULL, msginfo_list);
1385 case COMPOSE_REDIRECT:
1386 compose = compose_redirect(NULL, msginfo, FALSE);
1389 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1392 if (compose == NULL) {
1393 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1397 compose->rmode = mode;
1398 switch (compose->rmode) {
1400 case COMPOSE_REPLY_WITH_QUOTE:
1401 case COMPOSE_REPLY_WITHOUT_QUOTE:
1402 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1403 debug_print("reply mode Normal\n");
1404 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1405 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1407 case COMPOSE_REPLY_TO_SENDER:
1408 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1409 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1410 debug_print("reply mode Sender\n");
1411 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1413 case COMPOSE_REPLY_TO_ALL:
1414 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1415 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1416 debug_print("reply mode All\n");
1417 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1419 case COMPOSE_REPLY_TO_LIST:
1420 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1421 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1422 debug_print("reply mode List\n");
1423 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1431 static Compose *compose_reply(MsgInfo *msginfo,
1432 ComposeQuoteMode quote_mode,
1438 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1439 to_sender, FALSE, body);
1442 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1443 ComposeQuoteMode quote_mode,
1448 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1449 to_sender, TRUE, body);
1452 static void compose_extract_original_charset(Compose *compose)
1454 MsgInfo *info = NULL;
1455 if (compose->replyinfo) {
1456 info = compose->replyinfo;
1457 } else if (compose->fwdinfo) {
1458 info = compose->fwdinfo;
1459 } else if (compose->targetinfo) {
1460 info = compose->targetinfo;
1463 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1464 MimeInfo *partinfo = mimeinfo;
1465 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1466 partinfo = procmime_mimeinfo_next(partinfo);
1468 compose->orig_charset =
1469 g_strdup(procmime_mimeinfo_get_parameter(
1470 partinfo, "charset"));
1472 procmime_mimeinfo_free_all(mimeinfo);
1476 #define SIGNAL_BLOCK(buffer) { \
1477 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1478 G_CALLBACK(compose_changed_cb), \
1480 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1481 G_CALLBACK(text_inserted), \
1485 #define SIGNAL_UNBLOCK(buffer) { \
1486 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1487 G_CALLBACK(compose_changed_cb), \
1489 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1490 G_CALLBACK(text_inserted), \
1494 static Compose *compose_generic_reply(MsgInfo *msginfo,
1495 ComposeQuoteMode quote_mode,
1496 gboolean to_all, gboolean to_ml,
1498 gboolean followup_and_reply_to,
1502 PrefsAccount *account = NULL;
1503 GtkTextView *textview;
1504 GtkTextBuffer *textbuf;
1505 gboolean quote = FALSE;
1506 const gchar *qmark = NULL;
1507 const gchar *body_fmt = NULL;
1508 gchar *s_system = NULL;
1510 cm_return_val_if_fail(msginfo != NULL, NULL);
1511 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1513 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1515 cm_return_val_if_fail(account != NULL, NULL);
1517 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1519 compose->updating = TRUE;
1521 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1522 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1524 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1525 if (!compose->replyinfo)
1526 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1528 compose_extract_original_charset(compose);
1530 if (msginfo->folder && msginfo->folder->ret_rcpt)
1531 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1533 /* Set save folder */
1534 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1535 gchar *folderidentifier;
1537 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1538 folderidentifier = folder_item_get_identifier(msginfo->folder);
1539 compose_set_save_to(compose, folderidentifier);
1540 g_free(folderidentifier);
1543 if (compose_parse_header(compose, msginfo) < 0) {
1544 compose->updating = FALSE;
1545 compose_destroy(compose);
1549 /* override from name according to folder properties */
1550 if (msginfo->folder && msginfo->folder->prefs &&
1551 msginfo->folder->prefs->reply_with_format &&
1552 msginfo->folder->prefs->reply_override_from_format &&
1553 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1558 /* decode \-escape sequences in the internal representation of the quote format */
1559 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1560 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1563 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1564 compose->gtkaspell);
1566 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1568 quote_fmt_scan_string(tmp);
1571 buf = quote_fmt_get_buffer();
1573 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1575 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1576 quote_fmt_reset_vartable();
1581 textview = (GTK_TEXT_VIEW(compose->text));
1582 textbuf = gtk_text_view_get_buffer(textview);
1583 compose_create_tags(textview, compose);
1585 undo_block(compose->undostruct);
1587 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1590 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1591 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1592 /* use the reply format of folder (if enabled), or the account's one
1593 (if enabled) or fallback to the global reply format, which is always
1594 enabled (even if empty), and use the relevant quotemark */
1596 if (msginfo->folder && msginfo->folder->prefs &&
1597 msginfo->folder->prefs->reply_with_format) {
1598 qmark = msginfo->folder->prefs->reply_quotemark;
1599 body_fmt = msginfo->folder->prefs->reply_body_format;
1601 } else if (account->reply_with_format) {
1602 qmark = account->reply_quotemark;
1603 body_fmt = account->reply_body_format;
1606 qmark = prefs_common.quotemark;
1607 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1608 body_fmt = gettext(prefs_common.quotefmt);
1615 /* empty quotemark is not allowed */
1616 if (qmark == NULL || *qmark == '\0')
1618 compose_quote_fmt(compose, compose->replyinfo,
1619 body_fmt, qmark, body, FALSE, TRUE,
1620 _("The body of the \"Reply\" template has an error at line %d."));
1621 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1622 quote_fmt_reset_vartable();
1624 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1625 gtkaspell_highlight_all(compose->gtkaspell);
1629 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1630 compose_force_encryption(compose, account, FALSE, s_system);
1633 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1634 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1635 compose_force_signing(compose, account, s_system);
1639 SIGNAL_BLOCK(textbuf);
1641 if (account->auto_sig)
1642 compose_insert_sig(compose, FALSE);
1644 compose_wrap_all(compose);
1646 SIGNAL_UNBLOCK(textbuf);
1648 gtk_widget_grab_focus(compose->text);
1650 undo_unblock(compose->undostruct);
1652 if (prefs_common.auto_exteditor)
1653 compose_exec_ext_editor(compose);
1655 compose->modified = FALSE;
1656 compose_set_title(compose);
1658 compose->updating = FALSE;
1659 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1660 SCROLL_TO_CURSOR(compose);
1662 if (compose->deferred_destroy) {
1663 compose_destroy(compose);
1671 #define INSERT_FW_HEADER(var, hdr) \
1672 if (msginfo->var && *msginfo->var) { \
1673 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1674 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1675 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1678 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1679 gboolean as_attach, const gchar *body,
1680 gboolean no_extedit,
1684 GtkTextView *textview;
1685 GtkTextBuffer *textbuf;
1688 cm_return_val_if_fail(msginfo != NULL, NULL);
1689 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1692 !(account = compose_guess_forward_account_from_msginfo
1694 account = cur_account;
1696 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1698 compose->updating = TRUE;
1699 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1700 if (!compose->fwdinfo)
1701 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1703 compose_extract_original_charset(compose);
1705 if (msginfo->subject && *msginfo->subject) {
1706 gchar *buf, *buf2, *p;
1708 buf = p = g_strdup(msginfo->subject);
1709 p += subject_get_prefix_length(p);
1710 memmove(buf, p, strlen(p) + 1);
1712 buf2 = g_strdup_printf("Fw: %s", buf);
1713 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1719 /* override from name according to folder properties */
1720 if (msginfo->folder && msginfo->folder->prefs &&
1721 msginfo->folder->prefs->forward_with_format &&
1722 msginfo->folder->prefs->forward_override_from_format &&
1723 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1727 MsgInfo *full_msginfo = NULL;
1730 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1732 full_msginfo = procmsg_msginfo_copy(msginfo);
1734 /* decode \-escape sequences in the internal representation of the quote format */
1735 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1736 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1739 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1740 compose->gtkaspell);
1742 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1744 quote_fmt_scan_string(tmp);
1747 buf = quote_fmt_get_buffer();
1749 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1751 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1752 quote_fmt_reset_vartable();
1755 procmsg_msginfo_free(full_msginfo);
1758 textview = GTK_TEXT_VIEW(compose->text);
1759 textbuf = gtk_text_view_get_buffer(textview);
1760 compose_create_tags(textview, compose);
1762 undo_block(compose->undostruct);
1766 msgfile = procmsg_get_message_file(msginfo);
1767 if (!is_file_exist(msgfile))
1768 g_warning("%s: file not exist\n", msgfile);
1770 compose_attach_append(compose, msgfile, msgfile,
1775 const gchar *qmark = NULL;
1776 const gchar *body_fmt = NULL;
1777 MsgInfo *full_msginfo;
1779 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1780 body_fmt = gettext(prefs_common.fw_quotefmt);
1784 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1786 full_msginfo = procmsg_msginfo_copy(msginfo);
1788 /* use the forward format of folder (if enabled), or the account's one
1789 (if enabled) or fallback to the global forward format, which is always
1790 enabled (even if empty), and use the relevant quotemark */
1791 if (msginfo->folder && msginfo->folder->prefs &&
1792 msginfo->folder->prefs->forward_with_format) {
1793 qmark = msginfo->folder->prefs->forward_quotemark;
1794 body_fmt = msginfo->folder->prefs->forward_body_format;
1796 } else if (account->forward_with_format) {
1797 qmark = account->forward_quotemark;
1798 body_fmt = account->forward_body_format;
1801 qmark = prefs_common.fw_quotemark;
1802 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1803 body_fmt = gettext(prefs_common.fw_quotefmt);
1808 /* empty quotemark is not allowed */
1809 if (qmark == NULL || *qmark == '\0')
1812 compose_quote_fmt(compose, full_msginfo,
1813 body_fmt, qmark, body, FALSE, TRUE,
1814 _("The body of the \"Forward\" template has an error at line %d."));
1815 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1816 quote_fmt_reset_vartable();
1817 compose_attach_parts(compose, msginfo);
1819 procmsg_msginfo_free(full_msginfo);
1821 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1822 gtkaspell_highlight_all(compose->gtkaspell);
1826 SIGNAL_BLOCK(textbuf);
1828 if (account->auto_sig)
1829 compose_insert_sig(compose, FALSE);
1831 compose_wrap_all(compose);
1833 SIGNAL_UNBLOCK(textbuf);
1835 gtk_text_buffer_get_start_iter(textbuf, &iter);
1836 gtk_text_buffer_place_cursor(textbuf, &iter);
1838 gtk_widget_grab_focus(compose->header_last->entry);
1840 if (!no_extedit && prefs_common.auto_exteditor)
1841 compose_exec_ext_editor(compose);
1844 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1845 gchar *folderidentifier;
1847 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1848 folderidentifier = folder_item_get_identifier(msginfo->folder);
1849 compose_set_save_to(compose, folderidentifier);
1850 g_free(folderidentifier);
1853 undo_unblock(compose->undostruct);
1855 compose->modified = FALSE;
1856 compose_set_title(compose);
1858 compose->updating = FALSE;
1859 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1860 SCROLL_TO_CURSOR(compose);
1862 if (compose->deferred_destroy) {
1863 compose_destroy(compose);
1867 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1872 #undef INSERT_FW_HEADER
1874 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1877 GtkTextView *textview;
1878 GtkTextBuffer *textbuf;
1882 gboolean single_mail = TRUE;
1884 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1886 if (g_slist_length(msginfo_list) > 1)
1887 single_mail = FALSE;
1889 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1890 if (((MsgInfo *)msginfo->data)->folder == NULL)
1893 /* guess account from first selected message */
1895 !(account = compose_guess_forward_account_from_msginfo
1896 (msginfo_list->data)))
1897 account = cur_account;
1899 cm_return_val_if_fail(account != NULL, NULL);
1901 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1902 if (msginfo->data) {
1903 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1904 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1908 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1909 g_warning("no msginfo_list");
1913 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1915 compose->updating = TRUE;
1917 /* override from name according to folder properties */
1918 if (msginfo_list->data) {
1919 MsgInfo *msginfo = msginfo_list->data;
1921 if (msginfo->folder && msginfo->folder->prefs &&
1922 msginfo->folder->prefs->forward_with_format &&
1923 msginfo->folder->prefs->forward_override_from_format &&
1924 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1929 /* decode \-escape sequences in the internal representation of the quote format */
1930 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1931 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1934 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1935 compose->gtkaspell);
1937 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1939 quote_fmt_scan_string(tmp);
1942 buf = quote_fmt_get_buffer();
1944 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1946 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1947 quote_fmt_reset_vartable();
1953 textview = GTK_TEXT_VIEW(compose->text);
1954 textbuf = gtk_text_view_get_buffer(textview);
1955 compose_create_tags(textview, compose);
1957 undo_block(compose->undostruct);
1958 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1959 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1961 if (!is_file_exist(msgfile))
1962 g_warning("%s: file not exist\n", msgfile);
1964 compose_attach_append(compose, msgfile, msgfile,
1970 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1971 if (info->subject && *info->subject) {
1972 gchar *buf, *buf2, *p;
1974 buf = p = g_strdup(info->subject);
1975 p += subject_get_prefix_length(p);
1976 memmove(buf, p, strlen(p) + 1);
1978 buf2 = g_strdup_printf("Fw: %s", buf);
1979 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1985 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1986 _("Fw: multiple emails"));
1989 SIGNAL_BLOCK(textbuf);
1991 if (account->auto_sig)
1992 compose_insert_sig(compose, FALSE);
1994 compose_wrap_all(compose);
1996 SIGNAL_UNBLOCK(textbuf);
1998 gtk_text_buffer_get_start_iter(textbuf, &iter);
1999 gtk_text_buffer_place_cursor(textbuf, &iter);
2001 gtk_widget_grab_focus(compose->header_last->entry);
2002 undo_unblock(compose->undostruct);
2003 compose->modified = FALSE;
2004 compose_set_title(compose);
2006 compose->updating = FALSE;
2007 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2008 SCROLL_TO_CURSOR(compose);
2010 if (compose->deferred_destroy) {
2011 compose_destroy(compose);
2015 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2020 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2022 GtkTextIter start = *iter;
2023 GtkTextIter end_iter;
2024 int start_pos = gtk_text_iter_get_offset(&start);
2026 if (!compose->account->sig_sep)
2029 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2030 start_pos+strlen(compose->account->sig_sep));
2032 /* check sig separator */
2033 str = gtk_text_iter_get_text(&start, &end_iter);
2034 if (!strcmp(str, compose->account->sig_sep)) {
2036 /* check end of line (\n) */
2037 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2038 start_pos+strlen(compose->account->sig_sep));
2039 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2040 start_pos+strlen(compose->account->sig_sep)+1);
2041 tmp = gtk_text_iter_get_text(&start, &end_iter);
2042 if (!strcmp(tmp,"\n")) {
2054 static void compose_colorize_signature(Compose *compose)
2056 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2058 GtkTextIter end_iter;
2059 gtk_text_buffer_get_start_iter(buffer, &iter);
2060 while (gtk_text_iter_forward_line(&iter))
2061 if (compose_is_sig_separator(compose, buffer, &iter)) {
2062 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2063 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2067 #define BLOCK_WRAP() { \
2068 prev_autowrap = compose->autowrap; \
2069 buffer = gtk_text_view_get_buffer( \
2070 GTK_TEXT_VIEW(compose->text)); \
2071 compose->autowrap = FALSE; \
2073 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2074 G_CALLBACK(compose_changed_cb), \
2076 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2077 G_CALLBACK(text_inserted), \
2080 #define UNBLOCK_WRAP() { \
2081 compose->autowrap = prev_autowrap; \
2082 if (compose->autowrap) { \
2083 gint old = compose->draft_timeout_tag; \
2084 compose->draft_timeout_tag = -2; \
2085 compose_wrap_all(compose); \
2086 compose->draft_timeout_tag = old; \
2089 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2090 G_CALLBACK(compose_changed_cb), \
2092 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2093 G_CALLBACK(text_inserted), \
2097 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2099 Compose *compose = NULL;
2100 PrefsAccount *account = NULL;
2101 GtkTextView *textview;
2102 GtkTextBuffer *textbuf;
2106 gchar buf[BUFFSIZE];
2107 gboolean use_signing = FALSE;
2108 gboolean use_encryption = FALSE;
2109 gchar *privacy_system = NULL;
2110 int priority = PRIORITY_NORMAL;
2111 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2112 gboolean autowrap = prefs_common.autowrap;
2113 gboolean autoindent = prefs_common.auto_indent;
2115 cm_return_val_if_fail(msginfo != NULL, NULL);
2116 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2118 if (compose_put_existing_to_front(msginfo)) {
2122 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2123 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2124 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2125 gchar queueheader_buf[BUFFSIZE];
2128 /* Select Account from queue headers */
2129 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2130 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2131 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2132 account = account_find_from_id(id);
2134 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2135 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2136 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2137 account = account_find_from_id(id);
2139 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2140 sizeof(queueheader_buf), "NAID:")) {
2141 id = atoi(&queueheader_buf[strlen("NAID:")]);
2142 account = account_find_from_id(id);
2144 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2145 sizeof(queueheader_buf), "MAID:")) {
2146 id = atoi(&queueheader_buf[strlen("MAID:")]);
2147 account = account_find_from_id(id);
2149 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2150 sizeof(queueheader_buf), "S:")) {
2151 account = account_find_from_address(queueheader_buf, FALSE);
2153 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2154 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2155 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2156 use_signing = param;
2159 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2160 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2161 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2162 use_signing = param;
2165 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2166 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2167 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2168 use_encryption = param;
2170 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2171 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2172 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2173 use_encryption = param;
2175 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2176 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2177 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2180 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2181 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2182 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2185 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2186 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2187 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2189 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2190 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2191 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2193 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2194 sizeof(queueheader_buf), "X-Priority: ")) {
2195 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2198 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2199 sizeof(queueheader_buf), "RMID:")) {
2200 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2201 if (tokens[0] && tokens[1] && tokens[2]) {
2202 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2203 if (orig_item != NULL) {
2204 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2209 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2210 sizeof(queueheader_buf), "FMID:")) {
2211 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2212 if (tokens[0] && tokens[1] && tokens[2]) {
2213 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2214 if (orig_item != NULL) {
2215 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2221 account = msginfo->folder->folder->account;
2224 if (!account && prefs_common.reedit_account_autosel) {
2225 gchar from[BUFFSIZE];
2226 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2227 extract_address(from);
2228 account = account_find_from_address(from, FALSE);
2232 account = cur_account;
2234 cm_return_val_if_fail(account != NULL, NULL);
2236 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2238 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2239 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2240 compose->autowrap = autowrap;
2241 compose->replyinfo = replyinfo;
2242 compose->fwdinfo = fwdinfo;
2244 compose->updating = TRUE;
2245 compose->priority = priority;
2247 if (privacy_system != NULL) {
2248 compose->privacy_system = privacy_system;
2249 compose_use_signing(compose, use_signing);
2250 compose_use_encryption(compose, use_encryption);
2251 compose_update_privacy_system_menu_item(compose, FALSE);
2253 activate_privacy_system(compose, account, FALSE);
2256 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2258 compose_extract_original_charset(compose);
2260 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2261 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2262 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2263 gchar queueheader_buf[BUFFSIZE];
2265 /* Set message save folder */
2266 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2267 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2268 compose_set_save_to(compose, &queueheader_buf[4]);
2270 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2271 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2273 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2278 if (compose_parse_header(compose, msginfo) < 0) {
2279 compose->updating = FALSE;
2280 compose_destroy(compose);
2283 compose_reedit_set_entry(compose, msginfo);
2285 textview = GTK_TEXT_VIEW(compose->text);
2286 textbuf = gtk_text_view_get_buffer(textview);
2287 compose_create_tags(textview, compose);
2289 mark = gtk_text_buffer_get_insert(textbuf);
2290 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2292 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2293 G_CALLBACK(compose_changed_cb),
2296 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2297 fp = procmime_get_first_encrypted_text_content(msginfo);
2299 compose_force_encryption(compose, account, TRUE, NULL);
2302 fp = procmime_get_first_text_content(msginfo);
2305 g_warning("Can't get text part\n");
2309 gboolean prev_autowrap = compose->autowrap;
2310 GtkTextBuffer *buffer = textbuf;
2312 while (fgets(buf, sizeof(buf), fp) != NULL) {
2314 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2320 compose_attach_parts(compose, msginfo);
2322 compose_colorize_signature(compose);
2324 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2325 G_CALLBACK(compose_changed_cb),
2328 gtk_widget_grab_focus(compose->text);
2330 if (prefs_common.auto_exteditor) {
2331 compose_exec_ext_editor(compose);
2333 compose->modified = FALSE;
2334 compose_set_title(compose);
2336 compose->updating = FALSE;
2337 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2338 SCROLL_TO_CURSOR(compose);
2340 if (compose->deferred_destroy) {
2341 compose_destroy(compose);
2345 compose->sig_str = account_get_signature_str(compose->account);
2347 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2352 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2359 cm_return_val_if_fail(msginfo != NULL, NULL);
2362 account = account_get_reply_account(msginfo,
2363 prefs_common.reply_account_autosel);
2364 cm_return_val_if_fail(account != NULL, NULL);
2366 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2368 compose->updating = TRUE;
2370 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2371 compose->replyinfo = NULL;
2372 compose->fwdinfo = NULL;
2374 compose_show_first_last_header(compose, TRUE);
2376 gtk_widget_grab_focus(compose->header_last->entry);
2378 filename = procmsg_get_message_file(msginfo);
2380 if (filename == NULL) {
2381 compose->updating = FALSE;
2382 compose_destroy(compose);
2387 compose->redirect_filename = filename;
2389 /* Set save folder */
2390 item = msginfo->folder;
2391 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2392 gchar *folderidentifier;
2394 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2395 folderidentifier = folder_item_get_identifier(item);
2396 compose_set_save_to(compose, folderidentifier);
2397 g_free(folderidentifier);
2400 compose_attach_parts(compose, msginfo);
2402 if (msginfo->subject)
2403 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2405 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2407 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2408 _("The body of the \"Redirect\" template has an error at line %d."));
2409 quote_fmt_reset_vartable();
2410 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2412 compose_colorize_signature(compose);
2415 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2416 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2417 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2419 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2420 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2421 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2422 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2423 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2424 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2425 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2426 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2428 if (compose->toolbar->draft_btn)
2429 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2430 if (compose->toolbar->insert_btn)
2431 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2432 if (compose->toolbar->attach_btn)
2433 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2434 if (compose->toolbar->sig_btn)
2435 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2436 if (compose->toolbar->exteditor_btn)
2437 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2438 if (compose->toolbar->linewrap_current_btn)
2439 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2440 if (compose->toolbar->linewrap_all_btn)
2441 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2443 compose->modified = FALSE;
2444 compose_set_title(compose);
2445 compose->updating = FALSE;
2446 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2447 SCROLL_TO_CURSOR(compose);
2449 if (compose->deferred_destroy) {
2450 compose_destroy(compose);
2454 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2459 GList *compose_get_compose_list(void)
2461 return compose_list;
2464 void compose_entry_append(Compose *compose, const gchar *address,
2465 ComposeEntryType type, ComposePrefType pref_type)
2467 const gchar *header;
2469 gboolean in_quote = FALSE;
2470 if (!address || *address == '\0') return;
2477 header = N_("Bcc:");
2479 case COMPOSE_REPLYTO:
2480 header = N_("Reply-To:");
2482 case COMPOSE_NEWSGROUPS:
2483 header = N_("Newsgroups:");
2485 case COMPOSE_FOLLOWUPTO:
2486 header = N_( "Followup-To:");
2488 case COMPOSE_INREPLYTO:
2489 header = N_( "In-Reply-To:");
2496 header = prefs_common_translated_header_name(header);
2498 cur = begin = (gchar *)address;
2500 /* we separate the line by commas, but not if we're inside a quoted
2502 while (*cur != '\0') {
2504 in_quote = !in_quote;
2505 if (*cur == ',' && !in_quote) {
2506 gchar *tmp = g_strdup(begin);
2508 tmp[cur-begin]='\0';
2511 while (*tmp == ' ' || *tmp == '\t')
2513 compose_add_header_entry(compose, header, tmp, pref_type);
2520 gchar *tmp = g_strdup(begin);
2522 tmp[cur-begin]='\0';
2525 while (*tmp == ' ' || *tmp == '\t')
2527 compose_add_header_entry(compose, header, tmp, pref_type);
2532 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2534 static GdkColor yellow;
2535 static GdkColor black;
2536 static gboolean yellow_initialised = FALSE;
2540 if (!yellow_initialised) {
2541 gdk_color_parse("#f5f6be", &yellow);
2542 gdk_color_parse("#000000", &black);
2543 yellow_initialised = gdk_colormap_alloc_color(
2544 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2545 yellow_initialised &= gdk_colormap_alloc_color(
2546 gdk_colormap_get_system(), &black, FALSE, TRUE);
2549 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2550 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2551 if (gtk_entry_get_text(entry) &&
2552 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2553 if (yellow_initialised) {
2554 gtk_widget_modify_base(
2555 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2556 GTK_STATE_NORMAL, &yellow);
2557 gtk_widget_modify_text(
2558 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2559 GTK_STATE_NORMAL, &black);
2565 void compose_toolbar_cb(gint action, gpointer data)
2567 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2568 Compose *compose = (Compose*)toolbar_item->parent;
2570 cm_return_if_fail(compose != NULL);
2574 compose_send_cb(NULL, compose);
2577 compose_send_later_cb(NULL, compose);
2580 compose_draft(compose, COMPOSE_QUIT_EDITING);
2583 compose_insert_file_cb(NULL, compose);
2586 compose_attach_cb(NULL, compose);
2589 compose_insert_sig(compose, FALSE);
2592 compose_ext_editor_cb(NULL, compose);
2594 case A_LINEWRAP_CURRENT:
2595 compose_beautify_paragraph(compose, NULL, TRUE);
2597 case A_LINEWRAP_ALL:
2598 compose_wrap_all_full(compose, TRUE);
2601 compose_address_cb(NULL, compose);
2604 case A_CHECK_SPELLING:
2605 compose_check_all(NULL, compose);
2613 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2618 gchar *subject = NULL;
2622 gchar **attach = NULL;
2623 gchar *inreplyto = NULL;
2624 MailField mfield = NO_FIELD_PRESENT;
2626 /* get mailto parts but skip from */
2627 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2630 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2631 mfield = TO_FIELD_PRESENT;
2634 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2636 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2638 if (!g_utf8_validate (subject, -1, NULL)) {
2639 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2640 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2643 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2645 mfield = SUBJECT_FIELD_PRESENT;
2648 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2649 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2652 gboolean prev_autowrap = compose->autowrap;
2654 compose->autowrap = FALSE;
2656 mark = gtk_text_buffer_get_insert(buffer);
2657 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2659 if (!g_utf8_validate (body, -1, NULL)) {
2660 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2661 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2664 gtk_text_buffer_insert(buffer, &iter, body, -1);
2666 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2668 compose->autowrap = prev_autowrap;
2669 if (compose->autowrap)
2670 compose_wrap_all(compose);
2671 mfield = BODY_FIELD_PRESENT;
2675 gint i = 0, att = 0;
2676 gchar *warn_files = NULL;
2677 while (attach[i] != NULL) {
2678 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2679 if (utf8_filename) {
2680 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2681 gchar *tmp = g_strdup_printf("%s%s\n",
2682 warn_files?warn_files:"",
2688 g_free(utf8_filename);
2690 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2695 alertpanel_notice(ngettext(
2696 "The following file has been attached: \n%s",
2697 "The following files have been attached: \n%s", att), warn_files);
2702 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2715 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2717 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2718 {"Cc:", NULL, TRUE},
2719 {"References:", NULL, FALSE},
2720 {"Bcc:", NULL, TRUE},
2721 {"Newsgroups:", NULL, TRUE},
2722 {"Followup-To:", NULL, TRUE},
2723 {"List-Post:", NULL, FALSE},
2724 {"X-Priority:", NULL, FALSE},
2725 {NULL, NULL, FALSE}};
2741 cm_return_val_if_fail(msginfo != NULL, -1);
2743 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2744 procheader_get_header_fields(fp, hentry);
2747 if (hentry[H_REPLY_TO].body != NULL) {
2748 if (hentry[H_REPLY_TO].body[0] != '\0') {
2750 conv_unmime_header(hentry[H_REPLY_TO].body,
2753 g_free(hentry[H_REPLY_TO].body);
2754 hentry[H_REPLY_TO].body = NULL;
2756 if (hentry[H_CC].body != NULL) {
2757 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2758 g_free(hentry[H_CC].body);
2759 hentry[H_CC].body = NULL;
2761 if (hentry[H_REFERENCES].body != NULL) {
2762 if (compose->mode == COMPOSE_REEDIT)
2763 compose->references = hentry[H_REFERENCES].body;
2765 compose->references = compose_parse_references
2766 (hentry[H_REFERENCES].body, msginfo->msgid);
2767 g_free(hentry[H_REFERENCES].body);
2769 hentry[H_REFERENCES].body = NULL;
2771 if (hentry[H_BCC].body != NULL) {
2772 if (compose->mode == COMPOSE_REEDIT)
2774 conv_unmime_header(hentry[H_BCC].body, NULL);
2775 g_free(hentry[H_BCC].body);
2776 hentry[H_BCC].body = NULL;
2778 if (hentry[H_NEWSGROUPS].body != NULL) {
2779 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2780 hentry[H_NEWSGROUPS].body = NULL;
2782 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2783 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2784 compose->followup_to =
2785 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2788 g_free(hentry[H_FOLLOWUP_TO].body);
2789 hentry[H_FOLLOWUP_TO].body = NULL;
2791 if (hentry[H_LIST_POST].body != NULL) {
2792 gchar *to = NULL, *start = NULL;
2794 extract_address(hentry[H_LIST_POST].body);
2795 if (hentry[H_LIST_POST].body[0] != '\0') {
2796 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2798 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2799 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2802 g_free(compose->ml_post);
2803 compose->ml_post = to;
2806 g_free(hentry[H_LIST_POST].body);
2807 hentry[H_LIST_POST].body = NULL;
2810 /* CLAWS - X-Priority */
2811 if (compose->mode == COMPOSE_REEDIT)
2812 if (hentry[H_X_PRIORITY].body != NULL) {
2815 priority = atoi(hentry[H_X_PRIORITY].body);
2816 g_free(hentry[H_X_PRIORITY].body);
2818 hentry[H_X_PRIORITY].body = NULL;
2820 if (priority < PRIORITY_HIGHEST ||
2821 priority > PRIORITY_LOWEST)
2822 priority = PRIORITY_NORMAL;
2824 compose->priority = priority;
2827 if (compose->mode == COMPOSE_REEDIT) {
2828 if (msginfo->inreplyto && *msginfo->inreplyto)
2829 compose->inreplyto = g_strdup(msginfo->inreplyto);
2833 if (msginfo->msgid && *msginfo->msgid)
2834 compose->inreplyto = g_strdup(msginfo->msgid);
2836 if (!compose->references) {
2837 if (msginfo->msgid && *msginfo->msgid) {
2838 if (msginfo->inreplyto && *msginfo->inreplyto)
2839 compose->references =
2840 g_strdup_printf("<%s>\n\t<%s>",
2844 compose->references =
2845 g_strconcat("<", msginfo->msgid, ">",
2847 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2848 compose->references =
2849 g_strconcat("<", msginfo->inreplyto, ">",
2857 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2859 GSList *ref_id_list, *cur;
2863 ref_id_list = references_list_append(NULL, ref);
2864 if (!ref_id_list) return NULL;
2865 if (msgid && *msgid)
2866 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2871 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2872 /* "<" + Message-ID + ">" + CR+LF+TAB */
2873 len += strlen((gchar *)cur->data) + 5;
2875 if (len > MAX_REFERENCES_LEN) {
2876 /* remove second message-ID */
2877 if (ref_id_list && ref_id_list->next &&
2878 ref_id_list->next->next) {
2879 g_free(ref_id_list->next->data);
2880 ref_id_list = g_slist_remove
2881 (ref_id_list, ref_id_list->next->data);
2883 slist_free_strings(ref_id_list);
2884 g_slist_free(ref_id_list);
2891 new_ref = g_string_new("");
2892 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2893 if (new_ref->len > 0)
2894 g_string_append(new_ref, "\n\t");
2895 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2898 slist_free_strings(ref_id_list);
2899 g_slist_free(ref_id_list);
2901 new_ref_str = new_ref->str;
2902 g_string_free(new_ref, FALSE);
2907 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2908 const gchar *fmt, const gchar *qmark,
2909 const gchar *body, gboolean rewrap,
2910 gboolean need_unescape,
2911 const gchar *err_msg)
2913 MsgInfo* dummyinfo = NULL;
2914 gchar *quote_str = NULL;
2916 gboolean prev_autowrap;
2917 const gchar *trimmed_body = body;
2918 gint cursor_pos = -1;
2919 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2920 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2925 SIGNAL_BLOCK(buffer);
2928 dummyinfo = compose_msginfo_new_from_compose(compose);
2929 msginfo = dummyinfo;
2932 if (qmark != NULL) {
2934 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2935 compose->gtkaspell);
2937 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2939 quote_fmt_scan_string(qmark);
2942 buf = quote_fmt_get_buffer();
2944 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2946 Xstrdup_a(quote_str, buf, goto error)
2949 if (fmt && *fmt != '\0') {
2952 while (*trimmed_body == '\n')
2956 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2957 compose->gtkaspell);
2959 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2961 if (need_unescape) {
2964 /* decode \-escape sequences in the internal representation of the quote format */
2965 tmp = malloc(strlen(fmt)+1);
2966 pref_get_unescaped_pref(tmp, fmt);
2967 quote_fmt_scan_string(tmp);
2971 quote_fmt_scan_string(fmt);
2975 buf = quote_fmt_get_buffer();
2977 gint line = quote_fmt_get_line();
2978 alertpanel_error(err_msg, line);
2984 prev_autowrap = compose->autowrap;
2985 compose->autowrap = FALSE;
2987 mark = gtk_text_buffer_get_insert(buffer);
2988 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2989 if (g_utf8_validate(buf, -1, NULL)) {
2990 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2992 gchar *tmpout = NULL;
2993 tmpout = conv_codeset_strdup
2994 (buf, conv_get_locale_charset_str_no_utf8(),
2996 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2998 tmpout = g_malloc(strlen(buf)*2+1);
2999 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3001 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3005 cursor_pos = quote_fmt_get_cursor_pos();
3006 if (cursor_pos == -1)
3007 cursor_pos = gtk_text_iter_get_offset(&iter);
3008 compose->set_cursor_pos = cursor_pos;
3010 gtk_text_buffer_get_start_iter(buffer, &iter);
3011 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3012 gtk_text_buffer_place_cursor(buffer, &iter);
3014 compose->autowrap = prev_autowrap;
3015 if (compose->autowrap && rewrap)
3016 compose_wrap_all(compose);
3023 SIGNAL_UNBLOCK(buffer);
3025 procmsg_msginfo_free( dummyinfo );
3030 /* if ml_post is of type addr@host and from is of type
3031 * addr-anything@host, return TRUE
3033 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3035 gchar *left_ml = NULL;
3036 gchar *right_ml = NULL;
3037 gchar *left_from = NULL;
3038 gchar *right_from = NULL;
3039 gboolean result = FALSE;
3041 if (!ml_post || !from)
3044 left_ml = g_strdup(ml_post);
3045 if (strstr(left_ml, "@")) {
3046 right_ml = strstr(left_ml, "@")+1;
3047 *(strstr(left_ml, "@")) = '\0';
3050 left_from = g_strdup(from);
3051 if (strstr(left_from, "@")) {
3052 right_from = strstr(left_from, "@")+1;
3053 *(strstr(left_from, "@")) = '\0';
3056 if (left_ml && left_from && right_ml && right_from
3057 && !strncmp(left_from, left_ml, strlen(left_ml))
3058 && !strcmp(right_from, right_ml)) {
3067 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3068 gboolean to_all, gboolean to_ml,
3070 gboolean followup_and_reply_to)
3072 GSList *cc_list = NULL;
3075 gchar *replyto = NULL;
3076 gchar *ac_email = NULL;
3078 gboolean reply_to_ml = FALSE;
3079 gboolean default_reply_to = FALSE;
3081 cm_return_if_fail(compose->account != NULL);
3082 cm_return_if_fail(msginfo != NULL);
3084 reply_to_ml = to_ml && compose->ml_post;
3086 default_reply_to = msginfo->folder &&
3087 msginfo->folder->prefs->enable_default_reply_to;
3089 if (compose->account->protocol != A_NNTP) {
3090 if (msginfo && msginfo->folder && msginfo->folder->prefs) {
3091 if (msginfo->folder->prefs->enable_default_replyto) {
3092 compose_entry_append(compose, msginfo->folder->prefs->default_replyto,
3093 COMPOSE_REPLYTO, PREF_FOLDER);
3095 if (msginfo->folder->prefs->enable_default_bcc) {
3096 compose_entry_append(compose, msginfo->folder->prefs->default_bcc,
3097 COMPOSE_BCC, PREF_FOLDER);
3099 if (msginfo->folder->prefs->enable_default_cc) {
3100 compose_entry_append(compose, msginfo->folder->prefs->default_cc,
3101 COMPOSE_CC, PREF_FOLDER);
3104 if (reply_to_ml && !default_reply_to) {
3106 gboolean is_subscr = is_subscription(compose->ml_post,
3109 /* normal answer to ml post with a reply-to */
3110 compose_entry_append(compose,
3112 COMPOSE_TO, PREF_ML);
3113 if (compose->replyto)
3114 compose_entry_append(compose,
3116 COMPOSE_CC, PREF_ML);
3118 /* answer to subscription confirmation */
3119 if (compose->replyto)
3120 compose_entry_append(compose,
3122 COMPOSE_TO, PREF_ML);
3123 else if (msginfo->from)
3124 compose_entry_append(compose,
3126 COMPOSE_TO, PREF_ML);
3129 else if (!(to_all || to_sender) && default_reply_to) {
3130 compose_entry_append(compose,
3131 msginfo->folder->prefs->default_reply_to,
3132 COMPOSE_TO, PREF_FOLDER);
3133 compose_entry_mark_default_to(compose,
3134 msginfo->folder->prefs->default_reply_to);
3139 Xstrdup_a(tmp1, msginfo->from, return);
3140 extract_address(tmp1);
3141 if (to_all || to_sender ||
3142 !account_find_from_address(tmp1, FALSE))
3143 compose_entry_append(compose,
3144 (compose->replyto && !to_sender)
3145 ? compose->replyto :
3146 msginfo->from ? msginfo->from : "",
3147 COMPOSE_TO, PREF_NONE);
3148 else if (!to_all && !to_sender) {
3149 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3150 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3151 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3152 if (compose->replyto) {
3153 compose_entry_append(compose,
3155 COMPOSE_TO, PREF_NONE);
3157 compose_entry_append(compose,
3158 msginfo->from ? msginfo->from : "",
3159 COMPOSE_TO, PREF_NONE);
3162 /* replying to own mail, use original recp */
3163 compose_entry_append(compose,
3164 msginfo->to ? msginfo->to : "",
3165 COMPOSE_TO, PREF_NONE);
3166 compose_entry_append(compose,
3167 msginfo->cc ? msginfo->cc : "",
3168 COMPOSE_CC, PREF_NONE);
3173 if (to_sender || (compose->followup_to &&
3174 !strncmp(compose->followup_to, "poster", 6)))
3175 compose_entry_append
3177 (compose->replyto ? compose->replyto :
3178 msginfo->from ? msginfo->from : ""),
3179 COMPOSE_TO, PREF_NONE);
3181 else if (followup_and_reply_to || to_all) {
3182 compose_entry_append
3184 (compose->replyto ? compose->replyto :
3185 msginfo->from ? msginfo->from : ""),
3186 COMPOSE_TO, PREF_NONE);
3188 compose_entry_append
3190 compose->followup_to ? compose->followup_to :
3191 compose->newsgroups ? compose->newsgroups : "",
3192 COMPOSE_NEWSGROUPS, PREF_NONE);
3195 compose_entry_append
3197 compose->followup_to ? compose->followup_to :
3198 compose->newsgroups ? compose->newsgroups : "",
3199 COMPOSE_NEWSGROUPS, PREF_NONE);
3202 if (msginfo->subject && *msginfo->subject) {
3206 buf = p = g_strdup(msginfo->subject);
3207 p += subject_get_prefix_length(p);
3208 memmove(buf, p, strlen(p) + 1);
3210 buf2 = g_strdup_printf("Re: %s", buf);
3211 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3216 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3218 if (to_ml && compose->ml_post) return;
3219 if (!to_all || compose->account->protocol == A_NNTP) return;
3221 if (compose->replyto) {
3222 Xstrdup_a(replyto, compose->replyto, return);
3223 extract_address(replyto);
3225 if (msginfo->from) {
3226 Xstrdup_a(from, msginfo->from, return);
3227 extract_address(from);
3230 if (replyto && from)
3231 cc_list = address_list_append_with_comments(cc_list, from);
3232 if (to_all && msginfo->folder &&
3233 msginfo->folder->prefs->enable_default_reply_to)
3234 cc_list = address_list_append_with_comments(cc_list,
3235 msginfo->folder->prefs->default_reply_to);
3236 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3237 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3239 ac_email = g_utf8_strdown(compose->account->address, -1);
3242 for (cur = cc_list; cur != NULL; cur = cur->next) {
3243 gchar *addr = g_utf8_strdown(cur->data, -1);
3244 extract_address(addr);
3246 if (strcmp(ac_email, addr))
3247 compose_entry_append(compose, (gchar *)cur->data,
3248 COMPOSE_CC, PREF_NONE);
3250 debug_print("Cc address same as compose account's, ignoring\n");
3255 slist_free_strings(cc_list);
3256 g_slist_free(cc_list);
3262 #define SET_ENTRY(entry, str) \
3265 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3268 #define SET_ADDRESS(type, str) \
3271 compose_entry_append(compose, str, type, PREF_NONE); \
3274 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3276 cm_return_if_fail(msginfo != NULL);
3278 SET_ENTRY(subject_entry, msginfo->subject);
3279 SET_ENTRY(from_name, msginfo->from);
3280 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3281 SET_ADDRESS(COMPOSE_CC, compose->cc);
3282 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3283 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3284 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3285 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3287 compose_update_priority_menu_item(compose);
3288 compose_update_privacy_system_menu_item(compose, FALSE);
3289 compose_show_first_last_header(compose, TRUE);
3295 static void compose_insert_sig(Compose *compose, gboolean replace)
3297 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3298 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3300 GtkTextIter iter, iter_end;
3301 gint cur_pos, ins_pos;
3302 gboolean prev_autowrap;
3303 gboolean found = FALSE;
3304 gboolean exists = FALSE;
3306 cm_return_if_fail(compose->account != NULL);
3310 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3311 G_CALLBACK(compose_changed_cb),
3314 mark = gtk_text_buffer_get_insert(buffer);
3315 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3316 cur_pos = gtk_text_iter_get_offset (&iter);
3319 gtk_text_buffer_get_end_iter(buffer, &iter);
3321 exists = (compose->sig_str != NULL);
3324 GtkTextIter first_iter, start_iter, end_iter;
3326 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3328 if (!exists || compose->sig_str[0] == '\0')
3331 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3332 compose->signature_tag);
3335 /* include previous \n\n */
3336 gtk_text_iter_backward_chars(&first_iter, 1);
3337 start_iter = first_iter;
3338 end_iter = first_iter;
3340 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3341 compose->signature_tag);
3342 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3343 compose->signature_tag);
3345 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3351 g_free(compose->sig_str);
3352 compose->sig_str = account_get_signature_str(compose->account);
3354 cur_pos = gtk_text_iter_get_offset(&iter);
3356 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3357 g_free(compose->sig_str);
3358 compose->sig_str = NULL;
3360 if (compose->sig_inserted == FALSE)
3361 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3362 compose->sig_inserted = TRUE;
3364 cur_pos = gtk_text_iter_get_offset(&iter);
3365 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3367 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3368 gtk_text_iter_forward_chars(&iter, 1);
3369 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3370 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3372 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3373 cur_pos = gtk_text_buffer_get_char_count (buffer);
3376 /* put the cursor where it should be
3377 * either where the quote_fmt says, either where it was */
3378 if (compose->set_cursor_pos < 0)
3379 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3381 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3382 compose->set_cursor_pos);
3384 compose->set_cursor_pos = -1;
3385 gtk_text_buffer_place_cursor(buffer, &iter);
3386 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3387 G_CALLBACK(compose_changed_cb),
3393 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3396 GtkTextBuffer *buffer;
3399 const gchar *cur_encoding;
3400 gchar buf[BUFFSIZE];
3403 gboolean prev_autowrap;
3404 gboolean badtxt = FALSE;
3405 struct stat file_stat;
3408 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3410 /* get the size of the file we are about to insert */
3411 ret = g_stat(file, &file_stat);
3413 gchar *shortfile = g_path_get_basename(file);
3414 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3416 return COMPOSE_INSERT_NO_FILE;
3417 } else if (prefs_common.warn_large_insert == TRUE) {
3419 /* ask user for confirmation if the file is large */
3420 if (prefs_common.warn_large_insert_size < 0 ||
3421 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3425 msg = g_strdup_printf(_("You are about to insert a file of %s "
3426 "in the message body. Are you sure you want to do that?"),
3427 to_human_readable(file_stat.st_size));
3428 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3429 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3432 /* do we ask for confirmation next time? */
3433 if (aval & G_ALERTDISABLE) {
3434 /* no confirmation next time, disable feature in preferences */
3435 aval &= ~G_ALERTDISABLE;
3436 prefs_common.warn_large_insert = FALSE;
3439 /* abort file insertion if user canceled action */
3440 if (aval != G_ALERTALTERNATE) {
3441 return COMPOSE_INSERT_NO_FILE;
3447 if ((fp = g_fopen(file, "rb")) == NULL) {
3448 FILE_OP_ERROR(file, "fopen");
3449 return COMPOSE_INSERT_READ_ERROR;
3452 prev_autowrap = compose->autowrap;
3453 compose->autowrap = FALSE;
3455 text = GTK_TEXT_VIEW(compose->text);
3456 buffer = gtk_text_view_get_buffer(text);
3457 mark = gtk_text_buffer_get_insert(buffer);
3458 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3460 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3461 G_CALLBACK(text_inserted),
3464 cur_encoding = conv_get_locale_charset_str_no_utf8();
3466 while (fgets(buf, sizeof(buf), fp) != NULL) {
3469 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3470 str = g_strdup(buf);
3472 str = conv_codeset_strdup
3473 (buf, cur_encoding, CS_INTERNAL);
3476 /* strip <CR> if DOS/Windows file,
3477 replace <CR> with <LF> if Macintosh file. */
3480 if (len > 0 && str[len - 1] != '\n') {
3482 if (str[len] == '\r') str[len] = '\n';
3485 gtk_text_buffer_insert(buffer, &iter, str, -1);
3489 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3490 G_CALLBACK(text_inserted),
3492 compose->autowrap = prev_autowrap;
3493 if (compose->autowrap)
3494 compose_wrap_all(compose);
3499 return COMPOSE_INSERT_INVALID_CHARACTER;
3501 return COMPOSE_INSERT_SUCCESS;
3504 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3505 const gchar *filename,
3506 const gchar *content_type)
3514 GtkListStore *store;
3516 gboolean has_binary = FALSE;
3518 if (!is_file_exist(file)) {
3519 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3520 gboolean result = FALSE;
3521 if (file_from_uri && is_file_exist(file_from_uri)) {
3522 result = compose_attach_append(
3523 compose, file_from_uri,
3527 g_free(file_from_uri);
3530 alertpanel_error("File %s doesn't exist\n", filename);
3533 if ((size = get_file_size(file)) < 0) {
3534 alertpanel_error("Can't get file size of %s\n", filename);
3538 alertpanel_error(_("File %s is empty."), filename);
3541 if ((fp = g_fopen(file, "rb")) == NULL) {
3542 alertpanel_error(_("Can't read %s."), filename);
3547 ainfo = g_new0(AttachInfo, 1);
3548 auto_ainfo = g_auto_pointer_new_with_free
3549 (ainfo, (GFreeFunc) compose_attach_info_free);
3550 ainfo->file = g_strdup(file);
3553 ainfo->content_type = g_strdup(content_type);
3554 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3556 MsgFlags flags = {0, 0};
3558 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3559 ainfo->encoding = ENC_7BIT;
3561 ainfo->encoding = ENC_8BIT;
3563 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3564 if (msginfo && msginfo->subject)
3565 name = g_strdup(msginfo->subject);
3567 name = g_path_get_basename(filename ? filename : file);
3569 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3571 procmsg_msginfo_free(msginfo);
3573 if (!g_ascii_strncasecmp(content_type, "text", 4))
3574 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3576 ainfo->encoding = ENC_BASE64;
3577 name = g_path_get_basename(filename ? filename : file);
3578 ainfo->name = g_strdup(name);
3582 ainfo->content_type = procmime_get_mime_type(file);
3583 if (!ainfo->content_type) {
3584 ainfo->content_type =
3585 g_strdup("application/octet-stream");
3586 ainfo->encoding = ENC_BASE64;
3587 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3589 procmime_get_encoding_for_text_file(file, &has_binary);
3591 ainfo->encoding = ENC_BASE64;
3592 name = g_path_get_basename(filename ? filename : file);
3593 ainfo->name = g_strdup(name);
3597 if (ainfo->name != NULL
3598 && !strcmp(ainfo->name, ".")) {
3599 g_free(ainfo->name);
3603 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3604 g_free(ainfo->content_type);
3605 ainfo->content_type = g_strdup("application/octet-stream");
3608 ainfo->size = (goffset)size;
3609 size_text = to_human_readable((goffset)size);
3611 store = GTK_LIST_STORE(gtk_tree_view_get_model
3612 (GTK_TREE_VIEW(compose->attach_clist)));
3614 gtk_list_store_append(store, &iter);
3615 gtk_list_store_set(store, &iter,
3616 COL_MIMETYPE, ainfo->content_type,
3617 COL_SIZE, size_text,
3618 COL_NAME, ainfo->name,
3620 COL_AUTODATA, auto_ainfo,
3623 g_auto_pointer_free(auto_ainfo);
3624 compose_attach_update_label(compose);
3628 static void compose_use_signing(Compose *compose, gboolean use_signing)
3630 compose->use_signing = use_signing;
3631 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3634 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3636 compose->use_encryption = use_encryption;
3637 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3640 #define NEXT_PART_NOT_CHILD(info) \
3642 node = info->node; \
3643 while (node->children) \
3644 node = g_node_last_child(node); \
3645 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3648 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3652 MimeInfo *firsttext = NULL;
3653 MimeInfo *encrypted = NULL;
3656 const gchar *partname = NULL;
3658 mimeinfo = procmime_scan_message(msginfo);
3659 if (!mimeinfo) return;
3661 if (mimeinfo->node->children == NULL) {
3662 procmime_mimeinfo_free_all(mimeinfo);
3666 /* find first content part */
3667 child = (MimeInfo *) mimeinfo->node->children->data;
3668 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3669 child = (MimeInfo *)child->node->children->data;
3672 if (child->type == MIMETYPE_TEXT) {
3674 debug_print("First text part found\n");
3675 } else if (compose->mode == COMPOSE_REEDIT &&
3676 child->type == MIMETYPE_APPLICATION &&
3677 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3678 encrypted = (MimeInfo *)child->node->parent->data;
3681 child = (MimeInfo *) mimeinfo->node->children->data;
3682 while (child != NULL) {
3685 if (child == encrypted) {
3686 /* skip this part of tree */
3687 NEXT_PART_NOT_CHILD(child);
3691 if (child->type == MIMETYPE_MULTIPART) {
3692 /* get the actual content */
3693 child = procmime_mimeinfo_next(child);
3697 if (child == firsttext) {
3698 child = procmime_mimeinfo_next(child);
3702 outfile = procmime_get_tmp_file_name(child);
3703 if ((err = procmime_get_part(outfile, child)) < 0)
3704 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3706 gchar *content_type;
3708 content_type = procmime_get_content_type_str(child->type, child->subtype);
3710 /* if we meet a pgp signature, we don't attach it, but
3711 * we force signing. */
3712 if ((strcmp(content_type, "application/pgp-signature") &&
3713 strcmp(content_type, "application/pkcs7-signature") &&
3714 strcmp(content_type, "application/x-pkcs7-signature"))
3715 || compose->mode == COMPOSE_REDIRECT) {
3716 partname = procmime_mimeinfo_get_parameter(child, "filename");
3717 if (partname == NULL)
3718 partname = procmime_mimeinfo_get_parameter(child, "name");
3719 if (partname == NULL)
3721 compose_attach_append(compose, outfile,
3722 partname, content_type);
3724 compose_force_signing(compose, compose->account, NULL);
3726 g_free(content_type);
3729 NEXT_PART_NOT_CHILD(child);
3731 procmime_mimeinfo_free_all(mimeinfo);
3734 #undef NEXT_PART_NOT_CHILD
3739 WAIT_FOR_INDENT_CHAR,
3740 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3743 /* return indent length, we allow:
3744 indent characters followed by indent characters or spaces/tabs,
3745 alphabets and numbers immediately followed by indent characters,
3746 and the repeating sequences of the above
3747 If quote ends with multiple spaces, only the first one is included. */
3748 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3749 const GtkTextIter *start, gint *len)
3751 GtkTextIter iter = *start;
3755 IndentState state = WAIT_FOR_INDENT_CHAR;
3758 gint alnum_count = 0;
3759 gint space_count = 0;
3762 if (prefs_common.quote_chars == NULL) {
3766 while (!gtk_text_iter_ends_line(&iter)) {
3767 wc = gtk_text_iter_get_char(&iter);
3768 if (g_unichar_iswide(wc))
3770 clen = g_unichar_to_utf8(wc, ch);
3774 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3775 is_space = g_unichar_isspace(wc);
3777 if (state == WAIT_FOR_INDENT_CHAR) {
3778 if (!is_indent && !g_unichar_isalnum(wc))
3781 quote_len += alnum_count + space_count + 1;
3782 alnum_count = space_count = 0;
3783 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3786 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3787 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3791 else if (is_indent) {
3792 quote_len += alnum_count + space_count + 1;
3793 alnum_count = space_count = 0;
3796 state = WAIT_FOR_INDENT_CHAR;
3800 gtk_text_iter_forward_char(&iter);
3803 if (quote_len > 0 && space_count > 0)
3809 if (quote_len > 0) {
3811 gtk_text_iter_forward_chars(&iter, quote_len);
3812 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3818 /* return >0 if the line is itemized */
3819 static int compose_itemized_length(GtkTextBuffer *buffer,
3820 const GtkTextIter *start)
3822 GtkTextIter iter = *start;
3827 if (gtk_text_iter_ends_line(&iter))
3832 wc = gtk_text_iter_get_char(&iter);
3833 if (!g_unichar_isspace(wc))
3835 gtk_text_iter_forward_char(&iter);
3836 if (gtk_text_iter_ends_line(&iter))
3840 clen = g_unichar_to_utf8(wc, ch);
3844 if (!strchr("*-+", ch[0]))
3847 gtk_text_iter_forward_char(&iter);
3848 if (gtk_text_iter_ends_line(&iter))
3850 wc = gtk_text_iter_get_char(&iter);
3851 if (g_unichar_isspace(wc)) {
3857 /* return the string at the start of the itemization */
3858 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3859 const GtkTextIter *start)
3861 GtkTextIter iter = *start;
3864 GString *item_chars = g_string_new("");
3867 if (gtk_text_iter_ends_line(&iter))
3872 wc = gtk_text_iter_get_char(&iter);
3873 if (!g_unichar_isspace(wc))
3875 gtk_text_iter_forward_char(&iter);
3876 if (gtk_text_iter_ends_line(&iter))
3878 g_string_append_unichar(item_chars, wc);
3881 str = item_chars->str;
3882 g_string_free(item_chars, FALSE);
3886 /* return the number of spaces at a line's start */
3887 static int compose_left_offset_length(GtkTextBuffer *buffer,
3888 const GtkTextIter *start)
3890 GtkTextIter iter = *start;
3893 if (gtk_text_iter_ends_line(&iter))
3897 wc = gtk_text_iter_get_char(&iter);
3898 if (!g_unichar_isspace(wc))
3901 gtk_text_iter_forward_char(&iter);
3902 if (gtk_text_iter_ends_line(&iter))
3906 gtk_text_iter_forward_char(&iter);
3907 if (gtk_text_iter_ends_line(&iter))
3912 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3913 const GtkTextIter *start,
3914 GtkTextIter *break_pos,
3918 GtkTextIter iter = *start, line_end = *start;
3919 PangoLogAttr *attrs;
3926 gboolean can_break = FALSE;
3927 gboolean do_break = FALSE;
3928 gboolean was_white = FALSE;
3929 gboolean prev_dont_break = FALSE;
3931 gtk_text_iter_forward_to_line_end(&line_end);
3932 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3933 len = g_utf8_strlen(str, -1);
3937 g_warning("compose_get_line_break_pos: len = 0!\n");
3941 /* g_print("breaking line: %d: %s (len = %d)\n",
3942 gtk_text_iter_get_line(&iter), str, len); */
3944 attrs = g_new(PangoLogAttr, len + 1);
3946 pango_default_break(str, -1, NULL, attrs, len + 1);
3950 /* skip quote and leading spaces */
3951 for (i = 0; *p != '\0' && i < len; i++) {
3954 wc = g_utf8_get_char(p);
3955 if (i >= quote_len && !g_unichar_isspace(wc))
3957 if (g_unichar_iswide(wc))
3959 else if (*p == '\t')
3963 p = g_utf8_next_char(p);
3966 for (; *p != '\0' && i < len; i++) {
3967 PangoLogAttr *attr = attrs + i;
3971 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3974 was_white = attr->is_white;
3976 /* don't wrap URI */
3977 if ((uri_len = get_uri_len(p)) > 0) {
3979 if (pos > 0 && col > max_col) {
3989 wc = g_utf8_get_char(p);
3990 if (g_unichar_iswide(wc)) {
3992 if (prev_dont_break && can_break && attr->is_line_break)
3994 } else if (*p == '\t')
3998 if (pos > 0 && col > max_col) {
4003 if (*p == '-' || *p == '/')
4004 prev_dont_break = TRUE;
4006 prev_dont_break = FALSE;
4008 p = g_utf8_next_char(p);
4012 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4017 *break_pos = *start;
4018 gtk_text_iter_set_line_offset(break_pos, pos);
4023 static gboolean compose_join_next_line(Compose *compose,
4024 GtkTextBuffer *buffer,
4026 const gchar *quote_str)
4028 GtkTextIter iter_ = *iter, cur, prev, next, end;
4029 PangoLogAttr attrs[3];
4031 gchar *next_quote_str;
4034 gboolean keep_cursor = FALSE;
4036 if (!gtk_text_iter_forward_line(&iter_) ||
4037 gtk_text_iter_ends_line(&iter_)) {
4040 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4042 if ((quote_str || next_quote_str) &&
4043 strcmp2(quote_str, next_quote_str) != 0) {
4044 g_free(next_quote_str);
4047 g_free(next_quote_str);
4050 if (quote_len > 0) {
4051 gtk_text_iter_forward_chars(&end, quote_len);
4052 if (gtk_text_iter_ends_line(&end)) {
4057 /* don't join itemized lines */
4058 if (compose_itemized_length(buffer, &end) > 0) {
4062 /* don't join signature separator */
4063 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4066 /* delete quote str */
4068 gtk_text_buffer_delete(buffer, &iter_, &end);
4070 /* don't join line breaks put by the user */
4072 gtk_text_iter_backward_char(&cur);
4073 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4074 gtk_text_iter_forward_char(&cur);
4078 gtk_text_iter_forward_char(&cur);
4079 /* delete linebreak and extra spaces */
4080 while (gtk_text_iter_backward_char(&cur)) {
4081 wc1 = gtk_text_iter_get_char(&cur);
4082 if (!g_unichar_isspace(wc1))
4087 while (!gtk_text_iter_ends_line(&cur)) {
4088 wc1 = gtk_text_iter_get_char(&cur);
4089 if (!g_unichar_isspace(wc1))
4091 gtk_text_iter_forward_char(&cur);
4094 if (!gtk_text_iter_equal(&prev, &next)) {
4097 mark = gtk_text_buffer_get_insert(buffer);
4098 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4099 if (gtk_text_iter_equal(&prev, &cur))
4101 gtk_text_buffer_delete(buffer, &prev, &next);
4105 /* insert space if required */
4106 gtk_text_iter_backward_char(&prev);
4107 wc1 = gtk_text_iter_get_char(&prev);
4108 wc2 = gtk_text_iter_get_char(&next);
4109 gtk_text_iter_forward_char(&next);
4110 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4111 pango_default_break(str, -1, NULL, attrs, 3);
4112 if (!attrs[1].is_line_break ||
4113 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4114 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4116 gtk_text_iter_backward_char(&iter_);
4117 gtk_text_buffer_place_cursor(buffer, &iter_);
4126 #define ADD_TXT_POS(bp_, ep_, pti_) \
4127 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4128 last = last->next; \
4129 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4130 last->next = NULL; \
4132 g_warning("alloc error scanning URIs\n"); \
4135 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4137 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4138 GtkTextBuffer *buffer;
4139 GtkTextIter iter, break_pos, end_of_line;
4140 gchar *quote_str = NULL;
4142 gboolean wrap_quote = prefs_common.linewrap_quote;
4143 gboolean prev_autowrap = compose->autowrap;
4144 gint startq_offset = -1, noq_offset = -1;
4145 gint uri_start = -1, uri_stop = -1;
4146 gint nouri_start = -1, nouri_stop = -1;
4147 gint num_blocks = 0;
4148 gint quotelevel = -1;
4149 gboolean modified = force;
4150 gboolean removed = FALSE;
4151 gboolean modified_before_remove = FALSE;
4153 gboolean start = TRUE;
4154 gint itemized_len = 0, rem_item_len = 0;
4155 gchar *itemized_chars = NULL;
4156 gboolean item_continuation = FALSE;
4161 if (compose->draft_timeout_tag == -2) {
4165 compose->autowrap = FALSE;
4167 buffer = gtk_text_view_get_buffer(text);
4168 undo_wrapping(compose->undostruct, TRUE);
4173 mark = gtk_text_buffer_get_insert(buffer);
4174 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4178 if (compose->draft_timeout_tag == -2) {
4179 if (gtk_text_iter_ends_line(&iter)) {
4180 while (gtk_text_iter_ends_line(&iter) &&
4181 gtk_text_iter_forward_line(&iter))
4184 while (gtk_text_iter_backward_line(&iter)) {
4185 if (gtk_text_iter_ends_line(&iter)) {
4186 gtk_text_iter_forward_line(&iter);
4192 /* move to line start */
4193 gtk_text_iter_set_line_offset(&iter, 0);
4196 itemized_len = compose_itemized_length(buffer, &iter);
4198 if (!itemized_len) {
4199 itemized_len = compose_left_offset_length(buffer, &iter);
4200 item_continuation = TRUE;
4204 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4206 /* go until paragraph end (empty line) */
4207 while (start || !gtk_text_iter_ends_line(&iter)) {
4208 gchar *scanpos = NULL;
4209 /* parse table - in order of priority */
4211 const gchar *needle; /* token */
4213 /* token search function */
4214 gchar *(*search) (const gchar *haystack,
4215 const gchar *needle);
4216 /* part parsing function */
4217 gboolean (*parse) (const gchar *start,
4218 const gchar *scanpos,
4222 /* part to URI function */
4223 gchar *(*build_uri) (const gchar *bp,
4227 static struct table parser[] = {
4228 {"http://", strcasestr, get_uri_part, make_uri_string},
4229 {"https://", strcasestr, get_uri_part, make_uri_string},
4230 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4231 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4232 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4233 {"www.", strcasestr, get_uri_part, make_http_string},
4234 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4235 {"@", strcasestr, get_email_part, make_email_string}
4237 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4238 gint last_index = PARSE_ELEMS;
4240 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4244 if (!prev_autowrap && num_blocks == 0) {
4246 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4247 G_CALLBACK(text_inserted),
4250 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4253 uri_start = uri_stop = -1;
4255 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4258 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4259 if (startq_offset == -1)
4260 startq_offset = gtk_text_iter_get_offset(&iter);
4261 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4262 if (quotelevel > 2) {
4263 /* recycle colors */
4264 if (prefs_common.recycle_quote_colors)
4273 if (startq_offset == -1)
4274 noq_offset = gtk_text_iter_get_offset(&iter);
4278 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4281 if (gtk_text_iter_ends_line(&iter)) {
4283 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4284 prefs_common.linewrap_len,
4286 GtkTextIter prev, next, cur;
4287 if (prev_autowrap != FALSE || force) {
4288 compose->automatic_break = TRUE;
4290 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4291 compose->automatic_break = FALSE;
4292 if (itemized_len && compose->autoindent) {
4293 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4294 if (!item_continuation)
4295 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4297 } else if (quote_str && wrap_quote) {
4298 compose->automatic_break = TRUE;
4300 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4301 compose->automatic_break = FALSE;
4302 if (itemized_len && compose->autoindent) {
4303 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4304 if (!item_continuation)
4305 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4309 /* remove trailing spaces */
4311 rem_item_len = itemized_len;
4312 while (compose->autoindent && rem_item_len-- > 0)
4313 gtk_text_iter_backward_char(&cur);
4314 gtk_text_iter_backward_char(&cur);
4317 while (!gtk_text_iter_starts_line(&cur)) {
4320 gtk_text_iter_backward_char(&cur);
4321 wc = gtk_text_iter_get_char(&cur);
4322 if (!g_unichar_isspace(wc))
4326 if (!gtk_text_iter_equal(&prev, &next)) {
4327 gtk_text_buffer_delete(buffer, &prev, &next);
4329 gtk_text_iter_forward_char(&break_pos);
4333 gtk_text_buffer_insert(buffer, &break_pos,
4337 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4339 /* move iter to current line start */
4340 gtk_text_iter_set_line_offset(&iter, 0);
4347 /* move iter to next line start */
4353 if (!prev_autowrap && num_blocks > 0) {
4355 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4356 G_CALLBACK(text_inserted),
4360 while (!gtk_text_iter_ends_line(&end_of_line)) {
4361 gtk_text_iter_forward_char(&end_of_line);
4363 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4365 nouri_start = gtk_text_iter_get_offset(&iter);
4366 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4368 walk_pos = gtk_text_iter_get_offset(&iter);
4369 /* FIXME: this looks phony. scanning for anything in the parse table */
4370 for (n = 0; n < PARSE_ELEMS; n++) {
4373 tmp = parser[n].search(walk, parser[n].needle);
4375 if (scanpos == NULL || tmp < scanpos) {
4384 /* check if URI can be parsed */
4385 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4386 (const gchar **)&ep, FALSE)
4387 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4391 strlen(parser[last_index].needle);
4394 uri_start = walk_pos + (bp - o_walk);
4395 uri_stop = walk_pos + (ep - o_walk);
4399 gtk_text_iter_forward_line(&iter);
4402 if (startq_offset != -1) {
4403 GtkTextIter startquote, endquote;
4404 gtk_text_buffer_get_iter_at_offset(
4405 buffer, &startquote, startq_offset);
4408 switch (quotelevel) {
4410 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4411 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4412 gtk_text_buffer_apply_tag_by_name(
4413 buffer, "quote0", &startquote, &endquote);
4414 gtk_text_buffer_remove_tag_by_name(
4415 buffer, "quote1", &startquote, &endquote);
4416 gtk_text_buffer_remove_tag_by_name(
4417 buffer, "quote2", &startquote, &endquote);
4422 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4423 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4424 gtk_text_buffer_apply_tag_by_name(
4425 buffer, "quote1", &startquote, &endquote);
4426 gtk_text_buffer_remove_tag_by_name(
4427 buffer, "quote0", &startquote, &endquote);
4428 gtk_text_buffer_remove_tag_by_name(
4429 buffer, "quote2", &startquote, &endquote);
4434 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4435 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4436 gtk_text_buffer_apply_tag_by_name(
4437 buffer, "quote2", &startquote, &endquote);
4438 gtk_text_buffer_remove_tag_by_name(
4439 buffer, "quote0", &startquote, &endquote);
4440 gtk_text_buffer_remove_tag_by_name(
4441 buffer, "quote1", &startquote, &endquote);
4447 } else if (noq_offset != -1) {
4448 GtkTextIter startnoquote, endnoquote;
4449 gtk_text_buffer_get_iter_at_offset(
4450 buffer, &startnoquote, noq_offset);
4453 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4454 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4455 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4456 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4457 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4458 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4459 gtk_text_buffer_remove_tag_by_name(
4460 buffer, "quote0", &startnoquote, &endnoquote);
4461 gtk_text_buffer_remove_tag_by_name(
4462 buffer, "quote1", &startnoquote, &endnoquote);
4463 gtk_text_buffer_remove_tag_by_name(
4464 buffer, "quote2", &startnoquote, &endnoquote);
4470 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4471 GtkTextIter nouri_start_iter, nouri_end_iter;
4472 gtk_text_buffer_get_iter_at_offset(
4473 buffer, &nouri_start_iter, nouri_start);
4474 gtk_text_buffer_get_iter_at_offset(
4475 buffer, &nouri_end_iter, nouri_stop);
4476 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4477 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4478 gtk_text_buffer_remove_tag_by_name(
4479 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4480 modified_before_remove = modified;
4485 if (uri_start >= 0 && uri_stop > 0) {
4486 GtkTextIter uri_start_iter, uri_end_iter, back;
4487 gtk_text_buffer_get_iter_at_offset(
4488 buffer, &uri_start_iter, uri_start);
4489 gtk_text_buffer_get_iter_at_offset(
4490 buffer, &uri_end_iter, uri_stop);
4491 back = uri_end_iter;
4492 gtk_text_iter_backward_char(&back);
4493 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4494 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4495 gtk_text_buffer_apply_tag_by_name(
4496 buffer, "link", &uri_start_iter, &uri_end_iter);
4498 if (removed && !modified_before_remove) {
4504 // debug_print("not modified, out after %d lines\n", lines);
4508 // debug_print("modified, out after %d lines\n", lines);
4510 g_free(itemized_chars);
4513 undo_wrapping(compose->undostruct, FALSE);
4514 compose->autowrap = prev_autowrap;
4519 void compose_action_cb(void *data)
4521 Compose *compose = (Compose *)data;
4522 compose_wrap_all(compose);
4525 static void compose_wrap_all(Compose *compose)
4527 compose_wrap_all_full(compose, FALSE);
4530 static void compose_wrap_all_full(Compose *compose, gboolean force)
4532 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4533 GtkTextBuffer *buffer;
4535 gboolean modified = TRUE;
4537 buffer = gtk_text_view_get_buffer(text);
4539 gtk_text_buffer_get_start_iter(buffer, &iter);
4540 while (!gtk_text_iter_is_end(&iter) && modified)
4541 modified = compose_beautify_paragraph(compose, &iter, force);
4545 static void compose_set_title(Compose *compose)
4551 edited = compose->modified ? _(" [Edited]") : "";
4553 subject = gtk_editable_get_chars(
4554 GTK_EDITABLE(compose->subject_entry), 0, -1);
4556 #ifndef GENERIC_UMPC
4557 if (subject && strlen(subject))
4558 str = g_strdup_printf(_("%s - Compose message%s"),
4561 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4563 str = g_strdup(_("Compose message"));
4566 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4572 * compose_current_mail_account:
4574 * Find a current mail account (the currently selected account, or the
4575 * default account, if a news account is currently selected). If a
4576 * mail account cannot be found, display an error message.
4578 * Return value: Mail account, or NULL if not found.
4580 static PrefsAccount *
4581 compose_current_mail_account(void)
4585 if (cur_account && cur_account->protocol != A_NNTP)
4588 ac = account_get_default();
4589 if (!ac || ac->protocol == A_NNTP) {
4590 alertpanel_error(_("Account for sending mail is not specified.\n"
4591 "Please select a mail account before sending."));
4598 #define QUOTE_IF_REQUIRED(out, str) \
4600 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4604 len = strlen(str) + 3; \
4605 if ((__tmp = alloca(len)) == NULL) { \
4606 g_warning("can't allocate memory\n"); \
4607 g_string_free(header, TRUE); \
4610 g_snprintf(__tmp, len, "\"%s\"", str); \
4615 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4616 g_warning("can't allocate memory\n"); \
4617 g_string_free(header, TRUE); \
4620 strcpy(__tmp, str); \
4626 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4628 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4632 len = strlen(str) + 3; \
4633 if ((__tmp = alloca(len)) == NULL) { \
4634 g_warning("can't allocate memory\n"); \
4637 g_snprintf(__tmp, len, "\"%s\"", str); \
4642 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4643 g_warning("can't allocate memory\n"); \
4646 strcpy(__tmp, str); \
4652 static void compose_select_account(Compose *compose, PrefsAccount *account,
4655 gchar *from = NULL, *header;
4656 ComposeHeaderEntry *header_entry;
4658 cm_return_if_fail(account != NULL);
4660 compose->account = account;
4661 if (account->name && *account->name) {
4663 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4664 from = g_strdup_printf("%s <%s>",
4665 buf, account->address);
4666 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4668 from = g_strdup_printf("<%s>",
4670 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4675 compose_set_title(compose);
4677 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4678 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4680 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4681 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4682 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4684 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4686 activate_privacy_system(compose, account, FALSE);
4688 if (!init && compose->mode != COMPOSE_REDIRECT) {
4689 undo_block(compose->undostruct);
4690 compose_insert_sig(compose, TRUE);
4691 undo_unblock(compose->undostruct);
4694 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4695 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4697 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4698 if (account->protocol == A_NNTP) {
4699 if (!strcmp(header, _("To:")))
4700 combobox_select_by_text(
4701 GTK_COMBO_BOX(header_entry->combo),
4704 if (!strcmp(header, _("Newsgroups:")))
4705 combobox_select_by_text(
4706 GTK_COMBO_BOX(header_entry->combo),
4714 /* use account's dict info if set */
4715 if (compose->gtkaspell) {
4716 if (account->enable_default_dictionary)
4717 gtkaspell_change_dict(compose->gtkaspell,
4718 account->default_dictionary, FALSE);
4719 if (account->enable_default_alt_dictionary)
4720 gtkaspell_change_alt_dict(compose->gtkaspell,
4721 account->default_alt_dictionary);
4722 if (account->enable_default_dictionary
4723 || account->enable_default_alt_dictionary)
4724 compose_spell_menu_changed(compose);
4729 gboolean compose_check_for_valid_recipient(Compose *compose) {
4730 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4731 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4732 gboolean recipient_found = FALSE;
4736 /* free to and newsgroup list */
4737 slist_free_strings(compose->to_list);
4738 g_slist_free(compose->to_list);
4739 compose->to_list = NULL;
4741 slist_free_strings(compose->newsgroup_list);
4742 g_slist_free(compose->newsgroup_list);
4743 compose->newsgroup_list = NULL;
4745 /* search header entries for to and newsgroup entries */
4746 for (list = compose->header_list; list; list = list->next) {
4749 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4750 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4753 if (entry[0] != '\0') {
4754 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4755 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4756 compose->to_list = address_list_append(compose->to_list, entry);
4757 recipient_found = TRUE;
4760 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4761 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4762 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4763 recipient_found = TRUE;
4770 return recipient_found;
4773 static gboolean compose_check_for_set_recipients(Compose *compose)
4775 if (compose->account->set_autocc && compose->account->auto_cc) {
4776 gboolean found_other = FALSE;
4778 /* search header entries for to and newsgroup entries */
4779 for (list = compose->header_list; list; list = list->next) {
4782 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4783 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4786 if (strcmp(entry, compose->account->auto_cc)
4787 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4797 if (compose->batch) {
4798 gtk_widget_show_all(compose->window);
4800 aval = alertpanel(_("Send"),
4801 _("The only recipient is the default CC address. Send anyway?"),
4802 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4803 if (aval != G_ALERTALTERNATE)
4807 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4808 gboolean found_other = FALSE;
4810 /* search header entries for to and newsgroup entries */
4811 for (list = compose->header_list; list; list = list->next) {
4814 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4815 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4818 if (strcmp(entry, compose->account->auto_bcc)
4819 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4829 if (compose->batch) {
4830 gtk_widget_show_all(compose->window);
4832 aval = alertpanel(_("Send"),
4833 _("The only recipient is the default BCC address. Send anyway?"),
4834 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4835 if (aval != G_ALERTALTERNATE)
4842 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4846 if (compose_check_for_valid_recipient(compose) == FALSE) {
4847 if (compose->batch) {
4848 gtk_widget_show_all(compose->window);
4850 alertpanel_error(_("Recipient is not specified."));
4854 if (compose_check_for_set_recipients(compose) == FALSE) {
4858 if (!compose->batch) {
4859 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4860 if (*str == '\0' && check_everything == TRUE &&
4861 compose->mode != COMPOSE_REDIRECT) {
4863 gchar *button_label;
4866 if (compose->sending)
4867 button_label = _("+_Send");
4869 button_label = _("+_Queue");
4870 message = g_strdup_printf(_("Subject is empty. %s"),
4871 compose->sending?_("Send it anyway?"):
4872 _("Queue it anyway?"));
4874 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4875 GTK_STOCK_CANCEL, button_label, NULL);
4877 if (aval != G_ALERTALTERNATE)
4882 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4888 gint compose_send(Compose *compose)
4891 FolderItem *folder = NULL;
4893 gchar *msgpath = NULL;
4894 gboolean discard_window = FALSE;
4895 gchar *errstr = NULL;
4896 gchar *tmsgid = NULL;
4897 MainWindow *mainwin = mainwindow_get_mainwindow();
4898 gboolean queued_removed = FALSE;
4900 if (prefs_common.send_dialog_invisible
4901 || compose->batch == TRUE)
4902 discard_window = TRUE;
4904 compose_allow_user_actions (compose, FALSE);
4905 compose->sending = TRUE;
4907 if (compose_check_entries(compose, TRUE) == FALSE) {
4908 if (compose->batch) {
4909 gtk_widget_show_all(compose->window);
4915 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4918 if (compose->batch) {
4919 gtk_widget_show_all(compose->window);
4922 alertpanel_error(_("Could not queue message for sending:\n\n"
4923 "Charset conversion failed."));
4924 } else if (val == -5) {
4925 alertpanel_error(_("Could not queue message for sending:\n\n"
4926 "Couldn't get recipient encryption key."));
4927 } else if (val == -6) {
4929 } else if (val == -3) {
4930 if (privacy_peek_error())
4931 alertpanel_error(_("Could not queue message for sending:\n\n"
4932 "Signature failed: %s"), privacy_get_error());
4933 } else if (val == -2 && errno != 0) {
4934 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4936 alertpanel_error(_("Could not queue message for sending."));
4941 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4942 if (discard_window) {
4943 compose->sending = FALSE;
4944 compose_close(compose);
4945 /* No more compose access in the normal codepath
4946 * after this point! */
4951 alertpanel_error(_("The message was queued but could not be "
4952 "sent.\nUse \"Send queued messages\" from "
4953 "the main window to retry."));
4954 if (!discard_window) {
4961 if (msgpath == NULL) {
4962 msgpath = folder_item_fetch_msg(folder, msgnum);
4963 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4966 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4967 claws_unlink(msgpath);
4970 if (!discard_window) {
4972 if (!queued_removed)
4973 folder_item_remove_msg(folder, msgnum);
4974 folder_item_scan(folder);
4976 /* make sure we delete that */
4977 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4979 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4980 folder_item_remove_msg(folder, tmp->msgnum);
4981 procmsg_msginfo_free(tmp);
4988 if (!queued_removed)
4989 folder_item_remove_msg(folder, msgnum);
4990 folder_item_scan(folder);
4992 /* make sure we delete that */
4993 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4995 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4996 folder_item_remove_msg(folder, tmp->msgnum);
4997 procmsg_msginfo_free(tmp);
5000 if (!discard_window) {
5001 compose->sending = FALSE;
5002 compose_allow_user_actions (compose, TRUE);
5003 compose_close(compose);
5007 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5008 "the main window to retry."), errstr);
5011 alertpanel_error_log(_("The message was queued but could not be "
5012 "sent.\nUse \"Send queued messages\" from "
5013 "the main window to retry."));
5015 if (!discard_window) {
5024 toolbar_main_set_sensitive(mainwin);
5025 main_window_set_menu_sensitive(mainwin);
5031 compose_allow_user_actions (compose, TRUE);
5032 compose->sending = FALSE;
5033 compose->modified = TRUE;
5034 toolbar_main_set_sensitive(mainwin);
5035 main_window_set_menu_sensitive(mainwin);
5040 static gboolean compose_use_attach(Compose *compose)
5042 GtkTreeModel *model = gtk_tree_view_get_model
5043 (GTK_TREE_VIEW(compose->attach_clist));
5044 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5047 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5050 gchar buf[BUFFSIZE];
5052 gboolean first_to_address;
5053 gboolean first_cc_address;
5055 ComposeHeaderEntry *headerentry;
5056 const gchar *headerentryname;
5057 const gchar *cc_hdr;
5058 const gchar *to_hdr;
5059 gboolean err = FALSE;
5061 debug_print("Writing redirect header\n");
5063 cc_hdr = prefs_common_translated_header_name("Cc:");
5064 to_hdr = prefs_common_translated_header_name("To:");
5066 first_to_address = TRUE;
5067 for (list = compose->header_list; list; list = list->next) {
5068 headerentry = ((ComposeHeaderEntry *)list->data);
5069 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5071 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5072 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5073 Xstrdup_a(str, entstr, return -1);
5075 if (str[0] != '\0') {
5076 compose_convert_header
5077 (compose, buf, sizeof(buf), str,
5078 strlen("Resent-To") + 2, TRUE);
5080 if (first_to_address) {
5081 err |= (fprintf(fp, "Resent-To: ") < 0);
5082 first_to_address = FALSE;
5084 err |= (fprintf(fp, ",") < 0);
5086 err |= (fprintf(fp, "%s", buf) < 0);
5090 if (!first_to_address) {
5091 err |= (fprintf(fp, "\n") < 0);
5094 first_cc_address = TRUE;
5095 for (list = compose->header_list; list; list = list->next) {
5096 headerentry = ((ComposeHeaderEntry *)list->data);
5097 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5099 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5100 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5101 Xstrdup_a(str, strg, return -1);
5103 if (str[0] != '\0') {
5104 compose_convert_header
5105 (compose, buf, sizeof(buf), str,
5106 strlen("Resent-Cc") + 2, TRUE);
5108 if (first_cc_address) {
5109 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5110 first_cc_address = FALSE;
5112 err |= (fprintf(fp, ",") < 0);
5114 err |= (fprintf(fp, "%s", buf) < 0);
5118 if (!first_cc_address) {
5119 err |= (fprintf(fp, "\n") < 0);
5122 return (err ? -1:0);
5125 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5127 gchar buf[BUFFSIZE];
5129 const gchar *entstr;
5130 /* struct utsname utsbuf; */
5131 gboolean err = FALSE;
5133 cm_return_val_if_fail(fp != NULL, -1);
5134 cm_return_val_if_fail(compose->account != NULL, -1);
5135 cm_return_val_if_fail(compose->account->address != NULL, -1);
5138 get_rfc822_date(buf, sizeof(buf));
5139 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5142 if (compose->account->name && *compose->account->name) {
5143 compose_convert_header
5144 (compose, buf, sizeof(buf), compose->account->name,
5145 strlen("From: "), TRUE);
5146 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5147 buf, compose->account->address) < 0);
5149 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5152 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5153 if (*entstr != '\0') {
5154 Xstrdup_a(str, entstr, return -1);
5157 compose_convert_header(compose, buf, sizeof(buf), str,
5158 strlen("Subject: "), FALSE);
5159 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5163 /* Resent-Message-ID */
5164 if (compose->account->set_domain && compose->account->domain) {
5165 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5166 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5167 g_snprintf(buf, sizeof(buf), "%s",
5168 strchr(compose->account->address, '@') ?
5169 strchr(compose->account->address, '@')+1 :
5170 compose->account->address);
5172 g_snprintf(buf, sizeof(buf), "%s", "");
5175 if (compose->account->gen_msgid) {
5177 if (compose->account->msgid_with_addr) {
5178 addr = compose->account->address;
5180 generate_msgid(buf, sizeof(buf), addr);
5181 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5182 compose->msgid = g_strdup(buf);
5184 compose->msgid = NULL;
5187 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5190 /* separator between header and body */
5191 err |= (fputs("\n", fp) == EOF);
5193 return (err ? -1:0);
5196 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5200 gchar buf[BUFFSIZE];
5202 gboolean skip = FALSE;
5203 gboolean err = FALSE;
5204 gchar *not_included[]={
5205 "Return-Path:", "Delivered-To:", "Received:",
5206 "Subject:", "X-UIDL:", "AF:",
5207 "NF:", "PS:", "SRH:",
5208 "SFN:", "DSR:", "MID:",
5209 "CFG:", "PT:", "S:",
5210 "RQ:", "SSV:", "NSV:",
5211 "SSH:", "R:", "MAID:",
5212 "NAID:", "RMID:", "FMID:",
5213 "SCF:", "RRCPT:", "NG:",
5214 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5215 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5216 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5217 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5218 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5221 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5222 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5226 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5228 for (i = 0; not_included[i] != NULL; i++) {
5229 if (g_ascii_strncasecmp(buf, not_included[i],
5230 strlen(not_included[i])) == 0) {
5237 if (fputs(buf, fdest) == -1)
5240 if (!prefs_common.redirect_keep_from) {
5241 if (g_ascii_strncasecmp(buf, "From:",
5242 strlen("From:")) == 0) {
5243 err |= (fputs(" (by way of ", fdest) == EOF);
5244 if (compose->account->name
5245 && *compose->account->name) {
5246 compose_convert_header
5247 (compose, buf, sizeof(buf),
5248 compose->account->name,
5251 err |= (fprintf(fdest, "%s <%s>",
5253 compose->account->address) < 0);
5255 err |= (fprintf(fdest, "%s",
5256 compose->account->address) < 0);
5257 err |= (fputs(")", fdest) == EOF);
5261 if (fputs("\n", fdest) == -1)
5268 if (compose_redirect_write_headers(compose, fdest))
5271 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5272 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5285 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5287 GtkTextBuffer *buffer;
5288 GtkTextIter start, end;
5291 const gchar *out_codeset;
5292 EncodingType encoding = ENC_UNKNOWN;
5293 MimeInfo *mimemsg, *mimetext;
5295 const gchar *src_codeset = CS_INTERNAL;
5296 gchar *from_addr = NULL;
5297 gchar *from_name = NULL;
5299 if (action == COMPOSE_WRITE_FOR_SEND)
5300 attach_parts = TRUE;
5302 /* create message MimeInfo */
5303 mimemsg = procmime_mimeinfo_new();
5304 mimemsg->type = MIMETYPE_MESSAGE;
5305 mimemsg->subtype = g_strdup("rfc822");
5306 mimemsg->content = MIMECONTENT_MEM;
5307 mimemsg->tmp = TRUE; /* must free content later */
5308 mimemsg->data.mem = compose_get_header(compose);
5310 /* Create text part MimeInfo */
5311 /* get all composed text */
5312 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5313 gtk_text_buffer_get_start_iter(buffer, &start);
5314 gtk_text_buffer_get_end_iter(buffer, &end);
5315 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5317 out_codeset = conv_get_charset_str(compose->out_encoding);
5319 if (!out_codeset && is_ascii_str(chars)) {
5320 out_codeset = CS_US_ASCII;
5321 } else if (prefs_common.outgoing_fallback_to_ascii &&
5322 is_ascii_str(chars)) {
5323 out_codeset = CS_US_ASCII;
5324 encoding = ENC_7BIT;
5328 gchar *test_conv_global_out = NULL;
5329 gchar *test_conv_reply = NULL;
5331 /* automatic mode. be automatic. */
5332 codeconv_set_strict(TRUE);
5334 out_codeset = conv_get_outgoing_charset_str();
5336 debug_print("trying to convert to %s\n", out_codeset);
5337 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5340 if (!test_conv_global_out && compose->orig_charset
5341 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5342 out_codeset = compose->orig_charset;
5343 debug_print("failure; trying to convert to %s\n", out_codeset);
5344 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5347 if (!test_conv_global_out && !test_conv_reply) {
5349 out_codeset = CS_INTERNAL;
5350 debug_print("failure; finally using %s\n", out_codeset);
5352 g_free(test_conv_global_out);
5353 g_free(test_conv_reply);
5354 codeconv_set_strict(FALSE);
5357 if (encoding == ENC_UNKNOWN) {
5358 if (prefs_common.encoding_method == CTE_BASE64)
5359 encoding = ENC_BASE64;
5360 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5361 encoding = ENC_QUOTED_PRINTABLE;
5362 else if (prefs_common.encoding_method == CTE_8BIT)
5363 encoding = ENC_8BIT;
5365 encoding = procmime_get_encoding_for_charset(out_codeset);
5368 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5369 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5371 if (action == COMPOSE_WRITE_FOR_SEND) {
5372 codeconv_set_strict(TRUE);
5373 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5374 codeconv_set_strict(FALSE);
5380 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5381 "to the specified %s charset.\n"
5382 "Send it as %s?"), out_codeset, src_codeset);
5383 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5384 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5387 if (aval != G_ALERTALTERNATE) {
5392 out_codeset = src_codeset;
5398 out_codeset = src_codeset;
5403 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5404 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5405 strstr(buf, "\nFrom ") != NULL) {
5406 encoding = ENC_QUOTED_PRINTABLE;
5410 mimetext = procmime_mimeinfo_new();
5411 mimetext->content = MIMECONTENT_MEM;
5412 mimetext->tmp = TRUE; /* must free content later */
5413 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5414 * and free the data, which we need later. */
5415 mimetext->data.mem = g_strdup(buf);
5416 mimetext->type = MIMETYPE_TEXT;
5417 mimetext->subtype = g_strdup("plain");
5418 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5419 g_strdup(out_codeset));
5421 /* protect trailing spaces when signing message */
5422 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5423 privacy_system_can_sign(compose->privacy_system)) {
5424 encoding = ENC_QUOTED_PRINTABLE;
5427 debug_print("main text: %zd bytes encoded as %s in %d\n",
5428 strlen(buf), out_codeset, encoding);
5430 /* check for line length limit */
5431 if (action == COMPOSE_WRITE_FOR_SEND &&
5432 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5433 check_line_length(buf, 1000, &line) < 0) {
5437 msg = g_strdup_printf
5438 (_("Line %d exceeds the line length limit (998 bytes).\n"
5439 "The contents of the message might be broken on the way to the delivery.\n"
5441 "Send it anyway?"), line + 1);
5442 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5444 if (aval != G_ALERTALTERNATE) {
5450 if (encoding != ENC_UNKNOWN)
5451 procmime_encode_content(mimetext, encoding);
5453 /* append attachment parts */
5454 if (compose_use_attach(compose) && attach_parts) {
5455 MimeInfo *mimempart;
5456 gchar *boundary = NULL;
5457 mimempart = procmime_mimeinfo_new();
5458 mimempart->content = MIMECONTENT_EMPTY;
5459 mimempart->type = MIMETYPE_MULTIPART;
5460 mimempart->subtype = g_strdup("mixed");
5464 boundary = generate_mime_boundary(NULL);
5465 } while (strstr(buf, boundary) != NULL);
5467 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5470 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5472 g_node_append(mimempart->node, mimetext->node);
5473 g_node_append(mimemsg->node, mimempart->node);
5475 if (compose_add_attachments(compose, mimempart) < 0)
5478 g_node_append(mimemsg->node, mimetext->node);
5482 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5483 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5484 /* extract name and address */
5485 if (strstr(spec, " <") && strstr(spec, ">")) {
5486 from_addr = g_strdup(strrchr(spec, '<')+1);
5487 *(strrchr(from_addr, '>')) = '\0';
5488 from_name = g_strdup(spec);
5489 *(strrchr(from_name, '<')) = '\0';
5496 /* sign message if sending */
5497 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5498 privacy_system_can_sign(compose->privacy_system))
5499 if (!privacy_sign(compose->privacy_system, mimemsg,
5500 compose->account, from_addr)) {
5507 procmime_write_mimeinfo(mimemsg, fp);
5509 procmime_mimeinfo_free_all(mimemsg);
5514 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5516 GtkTextBuffer *buffer;
5517 GtkTextIter start, end;
5522 if ((fp = g_fopen(file, "wb")) == NULL) {
5523 FILE_OP_ERROR(file, "fopen");
5527 /* chmod for security */
5528 if (change_file_mode_rw(fp, file) < 0) {
5529 FILE_OP_ERROR(file, "chmod");
5530 g_warning("can't change file mode\n");
5533 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5534 gtk_text_buffer_get_start_iter(buffer, &start);
5535 gtk_text_buffer_get_end_iter(buffer, &end);
5536 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5538 chars = conv_codeset_strdup
5539 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5542 if (!chars) return -1;
5545 len = strlen(chars);
5546 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5547 FILE_OP_ERROR(file, "fwrite");
5556 if (fclose(fp) == EOF) {
5557 FILE_OP_ERROR(file, "fclose");
5564 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5567 MsgInfo *msginfo = compose->targetinfo;
5569 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5570 if (!msginfo) return -1;
5572 if (!force && MSG_IS_LOCKED(msginfo->flags))
5575 item = msginfo->folder;
5576 cm_return_val_if_fail(item != NULL, -1);
5578 if (procmsg_msg_exist(msginfo) &&
5579 (folder_has_parent_of_type(item, F_QUEUE) ||
5580 folder_has_parent_of_type(item, F_DRAFT)
5581 || msginfo == compose->autosaved_draft)) {
5582 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5583 g_warning("can't remove the old message\n");
5586 debug_print("removed reedit target %d\n", msginfo->msgnum);
5593 static void compose_remove_draft(Compose *compose)
5596 MsgInfo *msginfo = compose->targetinfo;
5597 drafts = account_get_special_folder(compose->account, F_DRAFT);
5599 if (procmsg_msg_exist(msginfo)) {
5600 folder_item_remove_msg(drafts, msginfo->msgnum);
5605 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5606 gboolean remove_reedit_target)
5608 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5611 static gboolean compose_warn_encryption(Compose *compose)
5613 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5614 AlertValue val = G_ALERTALTERNATE;
5616 if (warning == NULL)
5619 val = alertpanel_full(_("Encryption warning"), warning,
5620 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5621 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5622 if (val & G_ALERTDISABLE) {
5623 val &= ~G_ALERTDISABLE;
5624 if (val == G_ALERTALTERNATE)
5625 privacy_inhibit_encrypt_warning(compose->privacy_system,
5629 if (val == G_ALERTALTERNATE) {
5636 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5637 gchar **msgpath, gboolean check_subject,
5638 gboolean remove_reedit_target)
5645 static gboolean lock = FALSE;
5646 PrefsAccount *mailac = NULL, *newsac = NULL;
5647 gboolean err = FALSE;
5649 debug_print("queueing message...\n");
5650 cm_return_val_if_fail(compose->account != NULL, -1);
5654 if (compose_check_entries(compose, check_subject) == FALSE) {
5656 if (compose->batch) {
5657 gtk_widget_show_all(compose->window);
5662 if (!compose->to_list && !compose->newsgroup_list) {
5663 g_warning("can't get recipient list.");
5668 if (compose->to_list) {
5669 if (compose->account->protocol != A_NNTP)
5670 mailac = compose->account;
5671 else if (cur_account && cur_account->protocol != A_NNTP)
5672 mailac = cur_account;
5673 else if (!(mailac = compose_current_mail_account())) {
5675 alertpanel_error(_("No account for sending mails available!"));
5680 if (compose->newsgroup_list) {
5681 if (compose->account->protocol == A_NNTP)
5682 newsac = compose->account;
5685 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5690 /* write queue header */
5691 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5692 G_DIR_SEPARATOR, compose, (guint) rand());
5693 debug_print("queuing to %s\n", tmp);
5694 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5695 FILE_OP_ERROR(tmp, "fopen");
5701 if (change_file_mode_rw(fp, tmp) < 0) {
5702 FILE_OP_ERROR(tmp, "chmod");
5703 g_warning("can't change file mode\n");
5706 /* queueing variables */
5707 err |= (fprintf(fp, "AF:\n") < 0);
5708 err |= (fprintf(fp, "NF:0\n") < 0);
5709 err |= (fprintf(fp, "PS:10\n") < 0);
5710 err |= (fprintf(fp, "SRH:1\n") < 0);
5711 err |= (fprintf(fp, "SFN:\n") < 0);
5712 err |= (fprintf(fp, "DSR:\n") < 0);
5714 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5716 err |= (fprintf(fp, "MID:\n") < 0);
5717 err |= (fprintf(fp, "CFG:\n") < 0);
5718 err |= (fprintf(fp, "PT:0\n") < 0);
5719 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5720 err |= (fprintf(fp, "RQ:\n") < 0);
5722 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5724 err |= (fprintf(fp, "SSV:\n") < 0);
5726 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5728 err |= (fprintf(fp, "NSV:\n") < 0);
5729 err |= (fprintf(fp, "SSH:\n") < 0);
5730 /* write recepient list */
5731 if (compose->to_list) {
5732 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5733 for (cur = compose->to_list->next; cur != NULL;
5735 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5736 err |= (fprintf(fp, "\n") < 0);
5738 /* write newsgroup list */
5739 if (compose->newsgroup_list) {
5740 err |= (fprintf(fp, "NG:") < 0);
5741 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5742 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5743 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5744 err |= (fprintf(fp, "\n") < 0);
5746 /* Sylpheed account IDs */
5748 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5750 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5753 if (compose->privacy_system != NULL) {
5754 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5755 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5756 if (compose->use_encryption) {
5758 if (!compose_warn_encryption(compose)) {
5765 if (mailac && mailac->encrypt_to_self) {
5766 GSList *tmp_list = g_slist_copy(compose->to_list);
5767 tmp_list = g_slist_append(tmp_list, compose->account->address);
5768 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5769 g_slist_free(tmp_list);
5771 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5773 if (encdata != NULL) {
5774 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5775 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5776 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5778 } /* else we finally dont want to encrypt */
5780 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5781 /* and if encdata was null, it means there's been a problem in
5793 /* Save copy folder */
5794 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5795 gchar *savefolderid;
5797 savefolderid = compose_get_save_to(compose);
5798 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5799 g_free(savefolderid);
5801 /* Save copy folder */
5802 if (compose->return_receipt) {
5803 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5805 /* Message-ID of message replying to */
5806 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5809 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5810 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5813 /* Message-ID of message forwarding to */
5814 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5817 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5818 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5822 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5823 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5825 /* end of headers */
5826 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5828 if (compose->redirect_filename != NULL) {
5829 if (compose_redirect_write_to_file(compose, fp) < 0) {
5838 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5843 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5847 g_warning("failed to write queue message\n");
5854 if (fclose(fp) == EOF) {
5855 FILE_OP_ERROR(tmp, "fclose");
5862 if (item && *item) {
5865 queue = account_get_special_folder(compose->account, F_QUEUE);
5868 g_warning("can't find queue folder\n");
5874 folder_item_scan(queue);
5875 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5876 g_warning("can't queue the message\n");
5883 if (msgpath == NULL) {
5889 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5890 compose_remove_reedit_target(compose, FALSE);
5893 if ((msgnum != NULL) && (item != NULL)) {
5901 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5904 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5906 struct stat statbuf;
5907 gchar *type, *subtype;
5908 GtkTreeModel *model;
5911 model = gtk_tree_view_get_model(tree_view);
5913 if (!gtk_tree_model_get_iter_first(model, &iter))
5916 gtk_tree_model_get(model, &iter,
5920 if (!is_file_exist(ainfo->file)) {
5921 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5922 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5923 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5925 if (val == G_ALERTDEFAULT) {
5930 mimepart = procmime_mimeinfo_new();
5931 mimepart->content = MIMECONTENT_FILE;
5932 mimepart->data.filename = g_strdup(ainfo->file);
5933 mimepart->tmp = FALSE; /* or we destroy our attachment */
5934 mimepart->offset = 0;
5936 g_stat(ainfo->file, &statbuf);
5937 mimepart->length = statbuf.st_size;
5939 type = g_strdup(ainfo->content_type);
5941 if (!strchr(type, '/')) {
5943 type = g_strdup("application/octet-stream");
5946 subtype = strchr(type, '/') + 1;
5947 *(subtype - 1) = '\0';
5948 mimepart->type = procmime_get_media_type(type);
5949 mimepart->subtype = g_strdup(subtype);
5952 if (mimepart->type == MIMETYPE_MESSAGE &&
5953 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5954 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5957 if (mimepart->type == MIMETYPE_APPLICATION &&
5958 !strcmp2(mimepart->subtype, "octet-stream"))
5959 g_hash_table_insert(mimepart->typeparameters,
5960 g_strdup("name"), g_strdup(ainfo->name));
5961 g_hash_table_insert(mimepart->dispositionparameters,
5962 g_strdup("filename"), g_strdup(ainfo->name));
5963 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5967 if (compose->use_signing) {
5968 if (ainfo->encoding == ENC_7BIT)
5969 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5970 else if (ainfo->encoding == ENC_8BIT)
5971 ainfo->encoding = ENC_BASE64;
5974 procmime_encode_content(mimepart, ainfo->encoding);
5976 g_node_append(parent->node, mimepart->node);
5977 } while (gtk_tree_model_iter_next(model, &iter));
5982 #define IS_IN_CUSTOM_HEADER(header) \
5983 (compose->account->add_customhdr && \
5984 custom_header_find(compose->account->customhdr_list, header) != NULL)
5986 static void compose_add_headerfield_from_headerlist(Compose *compose,
5988 const gchar *fieldname,
5989 const gchar *seperator)
5991 gchar *str, *fieldname_w_colon;
5992 gboolean add_field = FALSE;
5994 ComposeHeaderEntry *headerentry;
5995 const gchar *headerentryname;
5996 const gchar *trans_fieldname;
5999 if (IS_IN_CUSTOM_HEADER(fieldname))
6002 debug_print("Adding %s-fields\n", fieldname);
6004 fieldstr = g_string_sized_new(64);
6006 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6007 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6009 for (list = compose->header_list; list; list = list->next) {
6010 headerentry = ((ComposeHeaderEntry *)list->data);
6011 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6013 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6014 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6016 if (str[0] != '\0') {
6018 g_string_append(fieldstr, seperator);
6019 g_string_append(fieldstr, str);
6028 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6029 compose_convert_header
6030 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6031 strlen(fieldname) + 2, TRUE);
6032 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6036 g_free(fieldname_w_colon);
6037 g_string_free(fieldstr, TRUE);
6042 static gchar *compose_get_header(Compose *compose)
6044 gchar buf[BUFFSIZE];
6045 const gchar *entry_str;
6049 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6051 gchar *from_name = NULL, *from_address = NULL;
6054 cm_return_val_if_fail(compose->account != NULL, NULL);
6055 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6057 header = g_string_sized_new(64);
6060 get_rfc822_date(buf, sizeof(buf));
6061 g_string_append_printf(header, "Date: %s\n", buf);
6065 if (compose->account->name && *compose->account->name) {
6067 QUOTE_IF_REQUIRED(buf, compose->account->name);
6068 tmp = g_strdup_printf("%s <%s>",
6069 buf, compose->account->address);
6071 tmp = g_strdup_printf("%s",
6072 compose->account->address);
6074 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6075 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6077 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6078 from_address = g_strdup(compose->account->address);
6080 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6081 /* extract name and address */
6082 if (strstr(spec, " <") && strstr(spec, ">")) {
6083 from_address = g_strdup(strrchr(spec, '<')+1);
6084 *(strrchr(from_address, '>')) = '\0';
6085 from_name = g_strdup(spec);
6086 *(strrchr(from_name, '<')) = '\0';
6089 from_address = g_strdup(spec);
6096 if (from_name && *from_name) {
6097 compose_convert_header
6098 (compose, buf, sizeof(buf), from_name,
6099 strlen("From: "), TRUE);
6100 QUOTE_IF_REQUIRED(name, buf);
6102 g_string_append_printf(header, "From: %s <%s>\n",
6103 name, from_address);
6105 g_string_append_printf(header, "From: %s\n", from_address);
6108 g_free(from_address);
6111 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6114 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6117 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6121 * If this account is a NNTP account remove Bcc header from
6122 * message body since it otherwise will be publicly shown
6124 if (compose->account->protocol != A_NNTP)
6125 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6128 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6130 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6133 compose_convert_header(compose, buf, sizeof(buf), str,
6134 strlen("Subject: "), FALSE);
6135 g_string_append_printf(header, "Subject: %s\n", buf);
6141 if (compose->account->set_domain && compose->account->domain) {
6142 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6143 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6144 g_snprintf(buf, sizeof(buf), "%s",
6145 strchr(compose->account->address, '@') ?
6146 strchr(compose->account->address, '@')+1 :
6147 compose->account->address);
6149 g_snprintf(buf, sizeof(buf), "%s", "");
6152 if (compose->account->gen_msgid) {
6154 if (compose->account->msgid_with_addr) {
6155 addr = compose->account->address;
6157 generate_msgid(buf, sizeof(buf), addr);
6158 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6159 compose->msgid = g_strdup(buf);
6161 compose->msgid = NULL;
6164 if (compose->remove_references == FALSE) {
6166 if (compose->inreplyto && compose->to_list)
6167 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6170 if (compose->references)
6171 g_string_append_printf(header, "References: %s\n", compose->references);
6175 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6178 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6181 if (compose->account->organization &&
6182 strlen(compose->account->organization) &&
6183 !IS_IN_CUSTOM_HEADER("Organization")) {
6184 compose_convert_header(compose, buf, sizeof(buf),
6185 compose->account->organization,
6186 strlen("Organization: "), FALSE);
6187 g_string_append_printf(header, "Organization: %s\n", buf);
6190 /* Program version and system info */
6191 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6192 !compose->newsgroup_list) {
6193 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6195 gtk_major_version, gtk_minor_version, gtk_micro_version,
6198 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6199 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6201 gtk_major_version, gtk_minor_version, gtk_micro_version,
6205 /* custom headers */
6206 if (compose->account->add_customhdr) {
6209 for (cur = compose->account->customhdr_list; cur != NULL;
6211 CustomHeader *chdr = (CustomHeader *)cur->data;
6213 if (custom_header_is_allowed(chdr->name)) {
6214 compose_convert_header
6215 (compose, buf, sizeof(buf),
6216 chdr->value ? chdr->value : "",
6217 strlen(chdr->name) + 2, FALSE);
6218 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6223 /* Automatic Faces and X-Faces */
6224 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6225 g_string_append_printf(header, "X-Face: %s\n", buf);
6227 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6228 g_string_append_printf(header, "X-Face: %s\n", buf);
6230 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6231 g_string_append_printf(header, "Face: %s\n", buf);
6233 else if (get_default_face (buf, sizeof(buf)) == 0) {
6234 g_string_append_printf(header, "Face: %s\n", buf);
6238 switch (compose->priority) {
6239 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6240 "X-Priority: 1 (Highest)\n");
6242 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6243 "X-Priority: 2 (High)\n");
6245 case PRIORITY_NORMAL: break;
6246 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6247 "X-Priority: 4 (Low)\n");
6249 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6250 "X-Priority: 5 (Lowest)\n");
6252 default: debug_print("compose: priority unknown : %d\n",
6256 /* Request Return Receipt */
6257 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6258 if (compose->return_receipt) {
6259 if (compose->account->name
6260 && *compose->account->name) {
6261 compose_convert_header(compose, buf, sizeof(buf),
6262 compose->account->name,
6263 strlen("Disposition-Notification-To: "),
6265 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6267 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6271 /* get special headers */
6272 for (list = compose->header_list; list; list = list->next) {
6273 ComposeHeaderEntry *headerentry;
6276 gchar *headername_wcolon;
6277 const gchar *headername_trans;
6280 gboolean standard_header = FALSE;
6282 headerentry = ((ComposeHeaderEntry *)list->data);
6284 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6286 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6291 if (!strstr(tmp, ":")) {
6292 headername_wcolon = g_strconcat(tmp, ":", NULL);
6293 headername = g_strdup(tmp);
6295 headername_wcolon = g_strdup(tmp);
6296 headername = g_strdup(strtok(tmp, ":"));
6300 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6301 Xstrdup_a(headervalue, entry_str, return NULL);
6302 subst_char(headervalue, '\r', ' ');
6303 subst_char(headervalue, '\n', ' ');
6304 string = std_headers;
6305 while (*string != NULL) {
6306 headername_trans = prefs_common_translated_header_name(*string);
6307 if (!strcmp(headername_trans, headername_wcolon))
6308 standard_header = TRUE;
6311 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6312 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6315 g_free(headername_wcolon);
6319 g_string_free(header, FALSE);
6324 #undef IS_IN_CUSTOM_HEADER
6326 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6327 gint header_len, gboolean addr_field)
6329 gchar *tmpstr = NULL;
6330 const gchar *out_codeset = NULL;
6332 cm_return_if_fail(src != NULL);
6333 cm_return_if_fail(dest != NULL);
6335 if (len < 1) return;
6337 tmpstr = g_strdup(src);
6339 subst_char(tmpstr, '\n', ' ');
6340 subst_char(tmpstr, '\r', ' ');
6343 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6344 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6345 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6350 codeconv_set_strict(TRUE);
6351 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6352 conv_get_charset_str(compose->out_encoding));
6353 codeconv_set_strict(FALSE);
6355 if (!dest || *dest == '\0') {
6356 gchar *test_conv_global_out = NULL;
6357 gchar *test_conv_reply = NULL;
6359 /* automatic mode. be automatic. */
6360 codeconv_set_strict(TRUE);
6362 out_codeset = conv_get_outgoing_charset_str();
6364 debug_print("trying to convert to %s\n", out_codeset);
6365 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6368 if (!test_conv_global_out && compose->orig_charset
6369 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6370 out_codeset = compose->orig_charset;
6371 debug_print("failure; trying to convert to %s\n", out_codeset);
6372 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6375 if (!test_conv_global_out && !test_conv_reply) {
6377 out_codeset = CS_INTERNAL;
6378 debug_print("finally using %s\n", out_codeset);
6380 g_free(test_conv_global_out);
6381 g_free(test_conv_reply);
6382 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6384 codeconv_set_strict(FALSE);
6389 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6393 cm_return_if_fail(user_data != NULL);
6395 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6396 g_strstrip(address);
6397 if (*address != '\0') {
6398 gchar *name = procheader_get_fromname(address);
6399 extract_address(address);
6400 addressbook_add_contact(name, address, NULL, NULL);
6405 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6407 GtkWidget *menuitem;
6410 cm_return_if_fail(menu != NULL);
6411 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6413 menuitem = gtk_separator_menu_item_new();
6414 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6415 gtk_widget_show(menuitem);
6417 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6418 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6420 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6421 g_strstrip(address);
6422 if (*address == '\0') {
6423 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6426 g_signal_connect(G_OBJECT(menuitem), "activate",
6427 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6428 gtk_widget_show(menuitem);
6431 static void compose_create_header_entry(Compose *compose)
6433 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6440 const gchar *header = NULL;
6441 ComposeHeaderEntry *headerentry;
6442 gboolean standard_header = FALSE;
6443 GtkListStore *model;
6445 #if !(GTK_CHECK_VERSION(2,12,0))
6446 GtkTooltips *tips = compose->tooltips;
6449 headerentry = g_new0(ComposeHeaderEntry, 1);
6452 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6453 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6454 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6456 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6458 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6460 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6461 COMPOSE_NEWSGROUPS);
6462 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6464 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6465 COMPOSE_FOLLOWUPTO);
6467 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6468 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6469 G_CALLBACK(compose_grab_focus_cb), compose);
6470 gtk_widget_show(combo);
6471 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6472 compose->header_nextrow, compose->header_nextrow+1,
6473 GTK_SHRINK, GTK_FILL, 0, 0);
6474 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6475 const gchar *last_header_entry = gtk_entry_get_text(
6476 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6478 while (*string != NULL) {
6479 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6480 standard_header = TRUE;
6483 if (standard_header)
6484 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6486 if (!compose->header_last || !standard_header) {
6487 switch(compose->account->protocol) {
6489 header = prefs_common_translated_header_name("Newsgroups:");
6492 header = prefs_common_translated_header_name("To:");
6497 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6499 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6500 G_CALLBACK(compose_grab_focus_cb), compose);
6502 /* Entry field with cleanup button */
6503 button = gtk_button_new();
6504 gtk_button_set_image(GTK_BUTTON(button),
6505 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6506 gtk_widget_show(button);
6507 CLAWS_SET_TIP(button,
6508 _("Delete entry contents"));
6509 entry = gtk_entry_new();
6510 gtk_widget_show(entry);
6511 CLAWS_SET_TIP(entry,
6512 _("Use <tab> to autocomplete from addressbook"));
6513 hbox = gtk_hbox_new (FALSE, 0);
6514 gtk_widget_show(hbox);
6515 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6516 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6517 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6518 compose->header_nextrow, compose->header_nextrow+1,
6519 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6521 g_signal_connect(G_OBJECT(entry), "key-press-event",
6522 G_CALLBACK(compose_headerentry_key_press_event_cb),
6524 g_signal_connect(G_OBJECT(entry), "changed",
6525 G_CALLBACK(compose_headerentry_changed_cb),
6527 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6528 G_CALLBACK(compose_grab_focus_cb), compose);
6530 g_signal_connect(G_OBJECT(button), "clicked",
6531 G_CALLBACK(compose_headerentry_button_clicked_cb),
6535 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6536 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6537 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6538 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6539 G_CALLBACK(compose_header_drag_received_cb),
6541 g_signal_connect(G_OBJECT(entry), "drag-drop",
6542 G_CALLBACK(compose_drag_drop),
6544 g_signal_connect(G_OBJECT(entry), "populate-popup",
6545 G_CALLBACK(compose_entry_popup_extend),
6548 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6550 headerentry->compose = compose;
6551 headerentry->combo = combo;
6552 headerentry->entry = entry;
6553 headerentry->button = button;
6554 headerentry->hbox = hbox;
6555 headerentry->headernum = compose->header_nextrow;
6556 headerentry->type = PREF_NONE;
6558 compose->header_nextrow++;
6559 compose->header_last = headerentry;
6560 compose->header_list =
6561 g_slist_append(compose->header_list,
6565 static void compose_add_header_entry(Compose *compose, const gchar *header,
6566 gchar *text, ComposePrefType pref_type)
6568 ComposeHeaderEntry *last_header = compose->header_last;
6569 gchar *tmp = g_strdup(text), *email;
6570 gboolean replyto_hdr;
6572 replyto_hdr = (!strcasecmp(header,
6573 prefs_common_translated_header_name("Reply-To:")) ||
6575 prefs_common_translated_header_name("Followup-To:")) ||
6577 prefs_common_translated_header_name("In-Reply-To:")));
6579 extract_address(tmp);
6580 email = g_utf8_strdown(tmp, -1);
6582 if (replyto_hdr == FALSE &&
6583 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6585 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6586 header, text, (gint) pref_type);
6592 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6593 gtk_entry_set_text(GTK_ENTRY(
6594 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6596 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6597 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6598 last_header->type = pref_type;
6600 if (replyto_hdr == FALSE)
6601 g_hash_table_insert(compose->email_hashtable, email,
6602 GUINT_TO_POINTER(1));
6609 static void compose_destroy_headerentry(Compose *compose,
6610 ComposeHeaderEntry *headerentry)
6612 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6615 extract_address(text);
6616 email = g_utf8_strdown(text, -1);
6617 g_hash_table_remove(compose->email_hashtable, email);
6621 gtk_widget_destroy(headerentry->combo);
6622 gtk_widget_destroy(headerentry->entry);
6623 gtk_widget_destroy(headerentry->button);
6624 gtk_widget_destroy(headerentry->hbox);
6625 g_free(headerentry);
6628 static void compose_remove_header_entries(Compose *compose)
6631 for (list = compose->header_list; list; list = list->next)
6632 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6634 compose->header_last = NULL;
6635 g_slist_free(compose->header_list);
6636 compose->header_list = NULL;
6637 compose->header_nextrow = 1;
6638 compose_create_header_entry(compose);
6641 static GtkWidget *compose_create_header(Compose *compose)
6643 GtkWidget *from_optmenu_hbox;
6644 GtkWidget *header_scrolledwin;
6645 GtkWidget *header_table;
6649 /* header labels and entries */
6650 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6651 gtk_widget_show(header_scrolledwin);
6652 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6654 header_table = gtk_table_new(2, 2, FALSE);
6655 gtk_widget_show(header_table);
6656 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6657 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6658 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6661 /* option menu for selecting accounts */
6662 from_optmenu_hbox = compose_account_option_menu_create(compose);
6663 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6664 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6667 compose->header_table = header_table;
6668 compose->header_list = NULL;
6669 compose->header_nextrow = count;
6671 compose_create_header_entry(compose);
6673 compose->table = NULL;
6675 return header_scrolledwin ;
6678 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6680 Compose *compose = (Compose *)data;
6681 GdkEventButton event;
6684 event.time = gtk_get_current_event_time();
6686 return attach_button_pressed(compose->attach_clist, &event, compose);
6689 static GtkWidget *compose_create_attach(Compose *compose)
6691 GtkWidget *attach_scrwin;
6692 GtkWidget *attach_clist;
6694 GtkListStore *store;
6695 GtkCellRenderer *renderer;
6696 GtkTreeViewColumn *column;
6697 GtkTreeSelection *selection;
6699 /* attachment list */
6700 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6701 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6702 GTK_POLICY_AUTOMATIC,
6703 GTK_POLICY_AUTOMATIC);
6704 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6706 store = gtk_list_store_new(N_ATTACH_COLS,
6711 G_TYPE_AUTO_POINTER,
6713 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6714 (GTK_TREE_MODEL(store)));
6715 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6716 g_object_unref(store);
6718 renderer = gtk_cell_renderer_text_new();
6719 column = gtk_tree_view_column_new_with_attributes
6720 (_("Mime type"), renderer, "text",
6721 COL_MIMETYPE, NULL);
6722 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6724 renderer = gtk_cell_renderer_text_new();
6725 column = gtk_tree_view_column_new_with_attributes
6726 (_("Size"), renderer, "text",
6728 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6730 renderer = gtk_cell_renderer_text_new();
6731 column = gtk_tree_view_column_new_with_attributes
6732 (_("Name"), renderer, "text",
6734 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6736 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6737 prefs_common.use_stripes_everywhere);
6738 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6739 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6741 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6742 G_CALLBACK(attach_selected), compose);
6743 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6744 G_CALLBACK(attach_button_pressed), compose);
6746 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6747 G_CALLBACK(popup_attach_button_pressed), compose);
6749 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6750 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6751 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6752 G_CALLBACK(popup_attach_button_pressed), compose);
6754 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6755 G_CALLBACK(attach_key_pressed), compose);
6758 gtk_drag_dest_set(attach_clist,
6759 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6760 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6761 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6762 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6763 G_CALLBACK(compose_attach_drag_received_cb),
6765 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6766 G_CALLBACK(compose_drag_drop),
6769 compose->attach_scrwin = attach_scrwin;
6770 compose->attach_clist = attach_clist;
6772 return attach_scrwin;
6775 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6776 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6778 static GtkWidget *compose_create_others(Compose *compose)
6781 GtkWidget *savemsg_checkbtn;
6782 GtkWidget *savemsg_combo;
6783 GtkWidget *savemsg_select;
6786 gchar *folderidentifier;
6788 /* Table for settings */
6789 table = gtk_table_new(3, 1, FALSE);
6790 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6791 gtk_widget_show(table);
6792 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6795 /* Save Message to folder */
6796 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6797 gtk_widget_show(savemsg_checkbtn);
6798 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6799 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6800 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6802 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6803 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6805 savemsg_combo = gtk_combo_box_entry_new_text();
6806 compose->savemsg_checkbtn = savemsg_checkbtn;
6807 compose->savemsg_combo = savemsg_combo;
6808 gtk_widget_show(savemsg_combo);
6810 if (prefs_common.compose_save_to_history)
6811 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6812 prefs_common.compose_save_to_history);
6814 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6815 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6816 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6817 G_CALLBACK(compose_grab_focus_cb), compose);
6818 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6819 folderidentifier = folder_item_get_identifier(account_get_special_folder
6820 (compose->account, F_OUTBOX));
6821 compose_set_save_to(compose, folderidentifier);
6822 g_free(folderidentifier);
6825 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6826 gtk_widget_show(savemsg_select);
6827 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6828 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6829 G_CALLBACK(compose_savemsg_select_cb),
6837 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6839 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6840 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6843 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6848 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6851 path = folder_item_get_identifier(dest);
6853 compose_set_save_to(compose, path);
6857 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6858 GdkAtom clip, GtkTextIter *insert_place);
6861 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6865 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6867 if (event->button == 3) {
6869 GtkTextIter sel_start, sel_end;
6870 gboolean stuff_selected;
6872 /* move the cursor to allow GtkAspell to check the word
6873 * under the mouse */
6874 if (event->x && event->y) {
6875 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6876 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6878 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6881 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6882 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6885 stuff_selected = gtk_text_buffer_get_selection_bounds(
6887 &sel_start, &sel_end);
6889 gtk_text_buffer_place_cursor (buffer, &iter);
6890 /* reselect stuff */
6892 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6893 gtk_text_buffer_select_range(buffer,
6894 &sel_start, &sel_end);
6896 return FALSE; /* pass the event so that the right-click goes through */
6899 if (event->button == 2) {
6904 /* get the middle-click position to paste at the correct place */
6905 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6906 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6908 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6911 entry_paste_clipboard(compose, text,
6912 prefs_common.linewrap_pastes,
6913 GDK_SELECTION_PRIMARY, &iter);
6921 static void compose_spell_menu_changed(void *data)
6923 Compose *compose = (Compose *)data;
6925 GtkWidget *menuitem;
6926 GtkWidget *parent_item;
6927 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6930 if (compose->gtkaspell == NULL)
6933 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6934 "/Menu/Spelling/Options");
6936 /* setting the submenu removes /Spelling/Options from the factory
6937 * so we need to save it */
6939 if (parent_item == NULL) {
6940 parent_item = compose->aspell_options_menu;
6941 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6943 compose->aspell_options_menu = parent_item;
6945 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6947 spell_menu = g_slist_reverse(spell_menu);
6948 for (items = spell_menu;
6949 items; items = items->next) {
6950 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6951 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6952 gtk_widget_show(GTK_WIDGET(menuitem));
6954 g_slist_free(spell_menu);
6956 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6957 gtk_widget_show(parent_item);
6960 static void compose_dict_changed(void *data)
6962 Compose *compose = (Compose *) data;
6964 if(compose->gtkaspell &&
6965 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6968 gtkaspell_highlight_all(compose->gtkaspell);
6969 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6973 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6975 Compose *compose = (Compose *)data;
6976 GdkEventButton event;
6979 event.time = gtk_get_current_event_time();
6983 return text_clicked(compose->text, &event, compose);
6986 static gboolean compose_force_window_origin = TRUE;
6987 static Compose *compose_create(PrefsAccount *account,
6996 GtkWidget *handlebox;
6998 GtkWidget *notebook;
7000 GtkWidget *attach_hbox;
7001 GtkWidget *attach_lab1;
7002 GtkWidget *attach_lab2;
7007 GtkWidget *subject_hbox;
7008 GtkWidget *subject_frame;
7009 GtkWidget *subject_entry;
7013 GtkWidget *edit_vbox;
7014 GtkWidget *ruler_hbox;
7016 GtkWidget *scrolledwin;
7018 GtkTextBuffer *buffer;
7019 GtkClipboard *clipboard;
7022 UndoMain *undostruct;
7024 gchar *titles[N_ATTACH_COLS];
7025 GtkWidget *popupmenu;
7026 GtkWidget *tmpl_menu;
7027 GtkActionGroup *action_group = NULL;
7030 GtkAspell * gtkaspell = NULL;
7033 static GdkGeometry geometry;
7035 cm_return_val_if_fail(account != NULL, NULL);
7037 debug_print("Creating compose window...\n");
7038 compose = g_new0(Compose, 1);
7040 titles[COL_MIMETYPE] = _("MIME type");
7041 titles[COL_SIZE] = _("Size");
7042 titles[COL_NAME] = _("Name");
7044 compose->batch = batch;
7045 compose->account = account;
7046 compose->folder = folder;
7048 compose->mutex = g_mutex_new();
7049 compose->set_cursor_pos = -1;
7051 #if !(GTK_CHECK_VERSION(2,12,0))
7052 compose->tooltips = tips;
7055 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7057 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7058 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7060 if (!geometry.max_width) {
7061 geometry.max_width = gdk_screen_width();
7062 geometry.max_height = gdk_screen_height();
7065 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7066 &geometry, GDK_HINT_MAX_SIZE);
7067 if (!geometry.min_width) {
7068 geometry.min_width = 600;
7069 geometry.min_height = 440;
7071 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7072 &geometry, GDK_HINT_MIN_SIZE);
7074 #ifndef GENERIC_UMPC
7075 if (compose_force_window_origin)
7076 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7077 prefs_common.compose_y);
7079 g_signal_connect(G_OBJECT(window), "delete_event",
7080 G_CALLBACK(compose_delete_cb), compose);
7081 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7082 gtk_widget_realize(window);
7084 gtkut_widget_set_composer_icon(window);
7086 vbox = gtk_vbox_new(FALSE, 0);
7087 gtk_container_add(GTK_CONTAINER(window), vbox);
7089 compose->ui_manager = gtk_ui_manager_new();
7090 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7091 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7092 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7093 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7094 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7095 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7096 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7097 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7098 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7099 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7107 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7110 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7112 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7133 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7152 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7191 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7212 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7219 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)
7220 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)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7226 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)
7227 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)
7229 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7231 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7232 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)
7233 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7235 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7236 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)
7237 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7239 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7241 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7242 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)
7243 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7245 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7247 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7248 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)
7249 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)
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7255 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7257 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7258 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7260 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7262 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)
7264 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7270 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7271 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7272 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7273 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7278 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7280 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7281 gtk_widget_show_all(menubar);
7283 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7285 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7287 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7290 if (prefs_common.toolbar_detachable) {
7291 handlebox = gtk_handle_box_new();
7293 handlebox = gtk_hbox_new(FALSE, 0);
7295 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7297 gtk_widget_realize(handlebox);
7299 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7302 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7306 vbox2 = gtk_vbox_new(FALSE, 2);
7307 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7308 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7311 notebook = gtk_notebook_new();
7312 gtk_widget_set_size_request(notebook, -1, 130);
7313 gtk_widget_show(notebook);
7315 /* header labels and entries */
7316 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7317 compose_create_header(compose),
7318 gtk_label_new_with_mnemonic(_("Hea_der")));
7319 /* attachment list */
7320 attach_hbox = gtk_hbox_new(FALSE, 0);
7321 gtk_widget_show(attach_hbox);
7323 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7324 gtk_widget_show(attach_lab1);
7325 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7327 attach_lab2 = gtk_label_new("");
7328 gtk_widget_show(attach_lab2);
7329 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7331 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7332 compose_create_attach(compose),
7335 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7336 compose_create_others(compose),
7337 gtk_label_new_with_mnemonic(_("Othe_rs")));
7340 subject_hbox = gtk_hbox_new(FALSE, 0);
7341 gtk_widget_show(subject_hbox);
7343 subject_frame = gtk_frame_new(NULL);
7344 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7345 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7346 gtk_widget_show(subject_frame);
7348 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7349 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7350 gtk_widget_show(subject);
7352 label = gtk_label_new(_("Subject:"));
7353 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7354 gtk_widget_show(label);
7357 subject_entry = claws_spell_entry_new();
7359 subject_entry = gtk_entry_new();
7361 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7362 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7363 G_CALLBACK(compose_grab_focus_cb), compose);
7364 gtk_widget_show(subject_entry);
7365 compose->subject_entry = subject_entry;
7366 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7368 edit_vbox = gtk_vbox_new(FALSE, 0);
7370 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7373 ruler_hbox = gtk_hbox_new(FALSE, 0);
7374 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7376 ruler = gtk_shruler_new();
7377 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7378 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7382 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7383 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7384 GTK_POLICY_AUTOMATIC,
7385 GTK_POLICY_AUTOMATIC);
7386 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7388 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7389 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7391 text = gtk_text_view_new();
7392 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7393 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7394 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7395 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7396 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7398 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7400 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7401 G_CALLBACK(compose_edit_size_alloc),
7403 g_signal_connect(G_OBJECT(buffer), "changed",
7404 G_CALLBACK(compose_changed_cb), compose);
7405 g_signal_connect(G_OBJECT(text), "grab_focus",
7406 G_CALLBACK(compose_grab_focus_cb), compose);
7407 g_signal_connect(G_OBJECT(buffer), "insert_text",
7408 G_CALLBACK(text_inserted), compose);
7409 g_signal_connect(G_OBJECT(text), "button_press_event",
7410 G_CALLBACK(text_clicked), compose);
7412 g_signal_connect(G_OBJECT(text), "popup-menu",
7413 G_CALLBACK(compose_popup_menu), compose);
7415 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7416 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7417 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7418 G_CALLBACK(compose_popup_menu), compose);
7420 g_signal_connect(G_OBJECT(subject_entry), "changed",
7421 G_CALLBACK(compose_changed_cb), compose);
7424 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7425 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7426 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7427 g_signal_connect(G_OBJECT(text), "drag_data_received",
7428 G_CALLBACK(compose_insert_drag_received_cb),
7430 g_signal_connect(G_OBJECT(text), "drag-drop",
7431 G_CALLBACK(compose_drag_drop),
7433 gtk_widget_show_all(vbox);
7435 /* pane between attach clist and text */
7436 paned = gtk_vpaned_new();
7437 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7439 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7440 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7442 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7444 gtk_paned_add1(GTK_PANED(paned), notebook);
7445 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7446 gtk_widget_show_all(paned);
7449 if (prefs_common.textfont) {
7450 PangoFontDescription *font_desc;
7452 font_desc = pango_font_description_from_string
7453 (prefs_common.textfont);
7455 gtk_widget_modify_font(text, font_desc);
7456 pango_font_description_free(font_desc);
7460 gtk_action_group_add_actions(action_group, compose_popup_entries,
7461 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7469 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7471 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7475 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7477 undostruct = undo_init(text);
7478 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7481 address_completion_start(window);
7483 compose->window = window;
7484 compose->vbox = vbox;
7485 compose->menubar = menubar;
7486 compose->handlebox = handlebox;
7488 compose->vbox2 = vbox2;
7490 compose->paned = paned;
7492 compose->attach_label = attach_lab2;
7494 compose->notebook = notebook;
7495 compose->edit_vbox = edit_vbox;
7496 compose->ruler_hbox = ruler_hbox;
7497 compose->ruler = ruler;
7498 compose->scrolledwin = scrolledwin;
7499 compose->text = text;
7501 compose->focused_editable = NULL;
7503 compose->popupmenu = popupmenu;
7505 compose->tmpl_menu = tmpl_menu;
7507 compose->mode = mode;
7508 compose->rmode = mode;
7510 compose->targetinfo = NULL;
7511 compose->replyinfo = NULL;
7512 compose->fwdinfo = NULL;
7514 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7515 g_str_equal, (GDestroyNotify) g_free, NULL);
7517 compose->replyto = NULL;
7519 compose->bcc = NULL;
7520 compose->followup_to = NULL;
7522 compose->ml_post = NULL;
7524 compose->inreplyto = NULL;
7525 compose->references = NULL;
7526 compose->msgid = NULL;
7527 compose->boundary = NULL;
7529 compose->autowrap = prefs_common.autowrap;
7530 compose->autoindent = prefs_common.auto_indent;
7531 compose->use_signing = FALSE;
7532 compose->use_encryption = FALSE;
7533 compose->privacy_system = NULL;
7535 compose->modified = FALSE;
7537 compose->return_receipt = FALSE;
7539 compose->to_list = NULL;
7540 compose->newsgroup_list = NULL;
7542 compose->undostruct = undostruct;
7544 compose->sig_str = NULL;
7546 compose->exteditor_file = NULL;
7547 compose->exteditor_pid = -1;
7548 compose->exteditor_tag = -1;
7549 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7552 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7553 if (mode != COMPOSE_REDIRECT) {
7554 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7555 strcmp(prefs_common.dictionary, "")) {
7556 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7557 prefs_common.alt_dictionary,
7558 conv_get_locale_charset_str(),
7559 prefs_common.misspelled_col,
7560 prefs_common.check_while_typing,
7561 prefs_common.recheck_when_changing_dict,
7562 prefs_common.use_alternate,
7563 prefs_common.use_both_dicts,
7564 GTK_TEXT_VIEW(text),
7565 GTK_WINDOW(compose->window),
7566 compose_dict_changed,
7567 compose_spell_menu_changed,
7570 alertpanel_error(_("Spell checker could not "
7572 gtkaspell_checkers_strerror());
7573 gtkaspell_checkers_reset_error();
7575 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7579 compose->gtkaspell = gtkaspell;
7580 compose_spell_menu_changed(compose);
7581 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7584 compose_select_account(compose, account, TRUE);
7586 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7589 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7590 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7592 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7593 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7595 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7596 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7598 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7599 if (account->protocol != A_NNTP)
7600 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7601 prefs_common_translated_header_name("To:"));
7603 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7604 prefs_common_translated_header_name("Newsgroups:"));
7606 addressbook_set_target_compose(compose);
7608 if (mode != COMPOSE_REDIRECT)
7609 compose_set_template_menu(compose);
7611 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7614 compose_list = g_list_append(compose_list, compose);
7616 if (!prefs_common.show_ruler)
7617 gtk_widget_hide(ruler_hbox);
7619 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7622 compose->priority = PRIORITY_NORMAL;
7623 compose_update_priority_menu_item(compose);
7625 compose_set_out_encoding(compose);
7628 compose_update_actions_menu(compose);
7630 /* Privacy Systems menu */
7631 compose_update_privacy_systems_menu(compose);
7633 activate_privacy_system(compose, account, TRUE);
7634 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7636 gtk_widget_realize(window);
7638 gtk_widget_show(window);
7640 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7641 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7648 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7653 GtkWidget *optmenubox;
7656 GtkWidget *from_name = NULL;
7657 #if !(GTK_CHECK_VERSION(2,12,0))
7658 GtkTooltips *tips = compose->tooltips;
7661 gint num = 0, def_menu = 0;
7663 accounts = account_get_list();
7664 cm_return_val_if_fail(accounts != NULL, NULL);
7666 optmenubox = gtk_event_box_new();
7667 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7668 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7670 hbox = gtk_hbox_new(FALSE, 6);
7671 from_name = gtk_entry_new();
7673 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7674 G_CALLBACK(compose_grab_focus_cb), compose);
7676 for (; accounts != NULL; accounts = accounts->next, num++) {
7677 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7678 gchar *name, *from = NULL;
7680 if (ac == compose->account) def_menu = num;
7682 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7685 if (ac == compose->account) {
7686 if (ac->name && *ac->name) {
7688 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7689 from = g_strdup_printf("%s <%s>",
7691 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7693 from = g_strdup_printf("%s",
7695 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7698 COMBOBOX_ADD(menu, name, ac->account_id);
7703 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7705 g_signal_connect(G_OBJECT(optmenu), "changed",
7706 G_CALLBACK(account_activated),
7708 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7709 G_CALLBACK(compose_entry_popup_extend),
7712 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7713 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7715 CLAWS_SET_TIP(optmenubox,
7716 _("Account to use for this email"));
7717 CLAWS_SET_TIP(from_name,
7718 _("Sender address to be used"));
7720 compose->from_name = from_name;
7725 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7727 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7728 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7729 Compose *compose = (Compose *) data;
7731 compose->priority = value;
7735 static void compose_reply_change_mode(Compose *compose,
7738 gboolean was_modified = compose->modified;
7740 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7742 cm_return_if_fail(compose->replyinfo != NULL);
7744 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7746 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7748 if (action == COMPOSE_REPLY_TO_ALL)
7750 if (action == COMPOSE_REPLY_TO_SENDER)
7752 if (action == COMPOSE_REPLY_TO_LIST)
7755 compose_remove_header_entries(compose);
7756 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7757 if (compose->account->set_autocc && compose->account->auto_cc)
7758 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7760 if (compose->account->set_autobcc && compose->account->auto_bcc)
7761 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7763 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7764 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7765 compose_show_first_last_header(compose, TRUE);
7766 compose->modified = was_modified;
7767 compose_set_title(compose);
7770 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7772 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7773 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7774 Compose *compose = (Compose *) data;
7777 compose_reply_change_mode(compose, value);
7780 static void compose_update_priority_menu_item(Compose * compose)
7782 GtkWidget *menuitem = NULL;
7783 switch (compose->priority) {
7784 case PRIORITY_HIGHEST:
7785 menuitem = gtk_ui_manager_get_widget
7786 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7789 menuitem = gtk_ui_manager_get_widget
7790 (compose->ui_manager, "/Menu/Options/Priority/High");
7792 case PRIORITY_NORMAL:
7793 menuitem = gtk_ui_manager_get_widget
7794 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7797 menuitem = gtk_ui_manager_get_widget
7798 (compose->ui_manager, "/Menu/Options/Priority/Low");
7800 case PRIORITY_LOWEST:
7801 menuitem = gtk_ui_manager_get_widget
7802 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7805 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7808 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7810 Compose *compose = (Compose *) data;
7812 gboolean can_sign = FALSE, can_encrypt = FALSE;
7814 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7816 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7819 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7820 g_free(compose->privacy_system);
7821 compose->privacy_system = NULL;
7822 if (systemid != NULL) {
7823 compose->privacy_system = g_strdup(systemid);
7825 can_sign = privacy_system_can_sign(systemid);
7826 can_encrypt = privacy_system_can_encrypt(systemid);
7829 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7831 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7832 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7835 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7837 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7838 GtkWidget *menuitem = NULL;
7840 gboolean can_sign = FALSE, can_encrypt = FALSE;
7841 gboolean found = FALSE;
7843 if (compose->privacy_system != NULL) {
7845 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7846 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7847 cm_return_if_fail(menuitem != NULL);
7849 amenu = GTK_MENU_SHELL(menuitem)->children;
7851 while (amenu != NULL) {
7852 GList *alist = amenu->next;
7854 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7855 if (systemid != NULL) {
7856 if (strcmp(systemid, compose->privacy_system) == 0 &&
7857 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7858 menuitem = GTK_WIDGET(amenu->data);
7860 can_sign = privacy_system_can_sign(systemid);
7861 can_encrypt = privacy_system_can_encrypt(systemid);
7865 } else if (strlen(compose->privacy_system) == 0 &&
7866 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7867 menuitem = GTK_WIDGET(amenu->data);
7870 can_encrypt = FALSE;
7877 if (menuitem != NULL)
7878 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7880 if (warn && !found && strlen(compose->privacy_system)) {
7881 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7882 "will not be able to sign or encrypt this message."),
7883 compose->privacy_system);
7887 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7888 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7891 static void compose_set_out_encoding(Compose *compose)
7893 CharSet out_encoding;
7894 const gchar *branch = NULL;
7895 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7897 switch(out_encoding) {
7898 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7899 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7900 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7901 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7902 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7903 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7904 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7905 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7906 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7907 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7908 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7909 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7910 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7911 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7912 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7913 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7914 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7915 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7916 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7917 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7918 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7919 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7920 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7921 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
7922 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7923 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7924 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7925 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7926 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7927 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7928 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7929 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7930 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7932 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7935 static void compose_set_template_menu(Compose *compose)
7937 GSList *tmpl_list, *cur;
7941 tmpl_list = template_get_config();
7943 menu = gtk_menu_new();
7945 gtk_menu_set_accel_group (GTK_MENU (menu),
7946 gtk_ui_manager_get_accel_group(compose->ui_manager));
7947 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7948 Template *tmpl = (Template *)cur->data;
7949 gchar *accel_path = NULL;
7950 item = gtk_menu_item_new_with_label(tmpl->name);
7951 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7952 g_signal_connect(G_OBJECT(item), "activate",
7953 G_CALLBACK(compose_template_activate_cb),
7955 g_object_set_data(G_OBJECT(item), "template", tmpl);
7956 gtk_widget_show(item);
7957 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7958 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7962 gtk_widget_show(menu);
7963 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7966 void compose_update_actions_menu(Compose *compose)
7968 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7971 static void compose_update_privacy_systems_menu(Compose *compose)
7973 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7974 GSList *systems, *cur;
7976 GtkWidget *system_none;
7978 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7979 GtkWidget *privacy_menu = gtk_menu_new();
7981 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7982 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7984 g_signal_connect(G_OBJECT(system_none), "activate",
7985 G_CALLBACK(compose_set_privacy_system_cb), compose);
7987 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7988 gtk_widget_show(system_none);
7990 systems = privacy_get_system_ids();
7991 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7992 gchar *systemid = cur->data;
7994 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7995 widget = gtk_radio_menu_item_new_with_label(group,
7996 privacy_system_get_name(systemid));
7997 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7998 g_strdup(systemid), g_free);
7999 g_signal_connect(G_OBJECT(widget), "activate",
8000 G_CALLBACK(compose_set_privacy_system_cb), compose);
8002 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8003 gtk_widget_show(widget);
8006 g_slist_free(systems);
8007 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8008 gtk_widget_show_all(privacy_menu);
8009 gtk_widget_show_all(privacy_menuitem);
8012 void compose_reflect_prefs_all(void)
8017 for (cur = compose_list; cur != NULL; cur = cur->next) {
8018 compose = (Compose *)cur->data;
8019 compose_set_template_menu(compose);
8023 void compose_reflect_prefs_pixmap_theme(void)
8028 for (cur = compose_list; cur != NULL; cur = cur->next) {
8029 compose = (Compose *)cur->data;
8030 toolbar_update(TOOLBAR_COMPOSE, compose);
8034 static const gchar *compose_quote_char_from_context(Compose *compose)
8036 const gchar *qmark = NULL;
8038 cm_return_val_if_fail(compose != NULL, NULL);
8040 switch (compose->mode) {
8041 /* use forward-specific quote char */
8042 case COMPOSE_FORWARD:
8043 case COMPOSE_FORWARD_AS_ATTACH:
8044 case COMPOSE_FORWARD_INLINE:
8045 if (compose->folder && compose->folder->prefs &&
8046 compose->folder->prefs->forward_with_format)
8047 qmark = compose->folder->prefs->forward_quotemark;
8048 else if (compose->account->forward_with_format)
8049 qmark = compose->account->forward_quotemark;
8051 qmark = prefs_common.fw_quotemark;
8054 /* use reply-specific quote char in all other modes */
8056 if (compose->folder && compose->folder->prefs &&
8057 compose->folder->prefs->reply_with_format)
8058 qmark = compose->folder->prefs->reply_quotemark;
8059 else if (compose->account->reply_with_format)
8060 qmark = compose->account->reply_quotemark;
8062 qmark = prefs_common.quotemark;
8066 if (qmark == NULL || *qmark == '\0')
8072 static void compose_template_apply(Compose *compose, Template *tmpl,
8076 GtkTextBuffer *buffer;
8080 gchar *parsed_str = NULL;
8081 gint cursor_pos = 0;
8082 const gchar *err_msg = _("The body of the template has an error at line %d.");
8085 /* process the body */
8087 text = GTK_TEXT_VIEW(compose->text);
8088 buffer = gtk_text_view_get_buffer(text);
8091 qmark = compose_quote_char_from_context(compose);
8093 if (compose->replyinfo != NULL) {
8096 gtk_text_buffer_set_text(buffer, "", -1);
8097 mark = gtk_text_buffer_get_insert(buffer);
8098 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8100 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8101 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8103 } else if (compose->fwdinfo != NULL) {
8106 gtk_text_buffer_set_text(buffer, "", -1);
8107 mark = gtk_text_buffer_get_insert(buffer);
8108 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8110 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8111 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8114 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8116 GtkTextIter start, end;
8119 gtk_text_buffer_get_start_iter(buffer, &start);
8120 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8121 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8123 /* clear the buffer now */
8125 gtk_text_buffer_set_text(buffer, "", -1);
8127 parsed_str = compose_quote_fmt(compose, dummyinfo,
8128 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8129 procmsg_msginfo_free( dummyinfo );
8135 gtk_text_buffer_set_text(buffer, "", -1);
8136 mark = gtk_text_buffer_get_insert(buffer);
8137 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8140 if (replace && parsed_str && compose->account->auto_sig)
8141 compose_insert_sig(compose, FALSE);
8143 if (replace && parsed_str) {
8144 gtk_text_buffer_get_start_iter(buffer, &iter);
8145 gtk_text_buffer_place_cursor(buffer, &iter);
8149 cursor_pos = quote_fmt_get_cursor_pos();
8150 compose->set_cursor_pos = cursor_pos;
8151 if (cursor_pos == -1)
8153 gtk_text_buffer_get_start_iter(buffer, &iter);
8154 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8155 gtk_text_buffer_place_cursor(buffer, &iter);
8158 /* process the other fields */
8160 compose_template_apply_fields(compose, tmpl);
8161 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8162 quote_fmt_reset_vartable();
8163 compose_changed_cb(NULL, compose);
8166 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8167 gtkaspell_highlight_all(compose->gtkaspell);
8171 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8173 MsgInfo* dummyinfo = NULL;
8174 MsgInfo *msginfo = NULL;
8177 if (compose->replyinfo != NULL)
8178 msginfo = compose->replyinfo;
8179 else if (compose->fwdinfo != NULL)
8180 msginfo = compose->fwdinfo;
8182 dummyinfo = compose_msginfo_new_from_compose(compose);
8183 msginfo = dummyinfo;
8186 if (tmpl->from && *tmpl->from != '\0') {
8188 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8189 compose->gtkaspell);
8191 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8193 quote_fmt_scan_string(tmpl->from);
8196 buf = quote_fmt_get_buffer();
8198 alertpanel_error(_("Template From format error."));
8200 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8204 if (tmpl->to && *tmpl->to != '\0') {
8206 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8207 compose->gtkaspell);
8209 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8211 quote_fmt_scan_string(tmpl->to);
8214 buf = quote_fmt_get_buffer();
8216 alertpanel_error(_("Template To format error."));
8218 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8222 if (tmpl->cc && *tmpl->cc != '\0') {
8224 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8225 compose->gtkaspell);
8227 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8229 quote_fmt_scan_string(tmpl->cc);
8232 buf = quote_fmt_get_buffer();
8234 alertpanel_error(_("Template Cc format error."));
8236 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8240 if (tmpl->bcc && *tmpl->bcc != '\0') {
8242 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8243 compose->gtkaspell);
8245 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8247 quote_fmt_scan_string(tmpl->bcc);
8250 buf = quote_fmt_get_buffer();
8252 alertpanel_error(_("Template Bcc format error."));
8254 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8258 /* process the subject */
8259 if (tmpl->subject && *tmpl->subject != '\0') {
8261 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8262 compose->gtkaspell);
8264 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8266 quote_fmt_scan_string(tmpl->subject);
8269 buf = quote_fmt_get_buffer();
8271 alertpanel_error(_("Template subject format error."));
8273 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8277 procmsg_msginfo_free( dummyinfo );
8280 static void compose_destroy(Compose *compose)
8282 GtkTextBuffer *buffer;
8283 GtkClipboard *clipboard;
8285 compose_list = g_list_remove(compose_list, compose);
8287 if (compose->updating) {
8288 debug_print("danger, not destroying anything now\n");
8289 compose->deferred_destroy = TRUE;
8292 /* NOTE: address_completion_end() does nothing with the window
8293 * however this may change. */
8294 address_completion_end(compose->window);
8296 slist_free_strings(compose->to_list);
8297 g_slist_free(compose->to_list);
8298 slist_free_strings(compose->newsgroup_list);
8299 g_slist_free(compose->newsgroup_list);
8300 slist_free_strings(compose->header_list);
8301 g_slist_free(compose->header_list);
8303 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8305 g_hash_table_destroy(compose->email_hashtable);
8307 procmsg_msginfo_free(compose->targetinfo);
8308 procmsg_msginfo_free(compose->replyinfo);
8309 procmsg_msginfo_free(compose->fwdinfo);
8311 g_free(compose->replyto);
8312 g_free(compose->cc);
8313 g_free(compose->bcc);
8314 g_free(compose->newsgroups);
8315 g_free(compose->followup_to);
8317 g_free(compose->ml_post);
8319 g_free(compose->inreplyto);
8320 g_free(compose->references);
8321 g_free(compose->msgid);
8322 g_free(compose->boundary);
8324 g_free(compose->redirect_filename);
8325 if (compose->undostruct)
8326 undo_destroy(compose->undostruct);
8328 g_free(compose->sig_str);
8330 g_free(compose->exteditor_file);
8332 g_free(compose->orig_charset);
8334 g_free(compose->privacy_system);
8336 if (addressbook_get_target_compose() == compose)
8337 addressbook_set_target_compose(NULL);
8340 if (compose->gtkaspell) {
8341 gtkaspell_delete(compose->gtkaspell);
8342 compose->gtkaspell = NULL;
8346 if (!compose->batch) {
8347 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8348 prefs_common.compose_height = compose->window->allocation.height;
8351 if (!gtk_widget_get_parent(compose->paned))
8352 gtk_widget_destroy(compose->paned);
8353 gtk_widget_destroy(compose->popupmenu);
8355 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8356 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8357 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8359 gtk_widget_destroy(compose->window);
8360 toolbar_destroy(compose->toolbar);
8361 g_free(compose->toolbar);
8362 g_mutex_free(compose->mutex);
8366 static void compose_attach_info_free(AttachInfo *ainfo)
8368 g_free(ainfo->file);
8369 g_free(ainfo->content_type);
8370 g_free(ainfo->name);
8374 static void compose_attach_update_label(Compose *compose)
8379 GtkTreeModel *model;
8384 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8385 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8386 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8390 while(gtk_tree_model_iter_next(model, &iter))
8393 text = g_strdup_printf("(%d)", i);
8394 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8398 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8400 Compose *compose = (Compose *)data;
8401 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8402 GtkTreeSelection *selection;
8404 GtkTreeModel *model;
8406 selection = gtk_tree_view_get_selection(tree_view);
8407 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8412 for (cur = sel; cur != NULL; cur = cur->next) {
8413 GtkTreePath *path = cur->data;
8414 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8417 gtk_tree_path_free(path);
8420 for (cur = sel; cur != NULL; cur = cur->next) {
8421 GtkTreeRowReference *ref = cur->data;
8422 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8425 if (gtk_tree_model_get_iter(model, &iter, path))
8426 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8428 gtk_tree_path_free(path);
8429 gtk_tree_row_reference_free(ref);
8433 compose_attach_update_label(compose);
8436 static struct _AttachProperty
8439 GtkWidget *mimetype_entry;
8440 GtkWidget *encoding_optmenu;
8441 GtkWidget *path_entry;
8442 GtkWidget *filename_entry;
8444 GtkWidget *cancel_btn;
8447 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8449 gtk_tree_path_free((GtkTreePath *)ptr);
8452 static void compose_attach_property(GtkAction *action, gpointer data)
8454 Compose *compose = (Compose *)data;
8455 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8457 GtkComboBox *optmenu;
8458 GtkTreeSelection *selection;
8460 GtkTreeModel *model;
8463 static gboolean cancelled;
8465 /* only if one selected */
8466 selection = gtk_tree_view_get_selection(tree_view);
8467 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8470 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8474 path = (GtkTreePath *) sel->data;
8475 gtk_tree_model_get_iter(model, &iter, path);
8476 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8479 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8485 if (!attach_prop.window)
8486 compose_attach_property_create(&cancelled);
8487 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8488 gtk_widget_grab_focus(attach_prop.ok_btn);
8489 gtk_widget_show(attach_prop.window);
8490 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8492 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8493 if (ainfo->encoding == ENC_UNKNOWN)
8494 combobox_select_by_data(optmenu, ENC_BASE64);
8496 combobox_select_by_data(optmenu, ainfo->encoding);
8498 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8499 ainfo->content_type ? ainfo->content_type : "");
8500 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8501 ainfo->file ? ainfo->file : "");
8502 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8503 ainfo->name ? ainfo->name : "");
8506 const gchar *entry_text;
8508 gchar *cnttype = NULL;
8515 gtk_widget_hide(attach_prop.window);
8516 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8521 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8522 if (*entry_text != '\0') {
8525 text = g_strstrip(g_strdup(entry_text));
8526 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8527 cnttype = g_strdup(text);
8530 alertpanel_error(_("Invalid MIME type."));
8536 ainfo->encoding = combobox_get_active_data(optmenu);
8538 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8539 if (*entry_text != '\0') {
8540 if (is_file_exist(entry_text) &&
8541 (size = get_file_size(entry_text)) > 0)
8542 file = g_strdup(entry_text);
8545 (_("File doesn't exist or is empty."));
8551 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8552 if (*entry_text != '\0') {
8553 g_free(ainfo->name);
8554 ainfo->name = g_strdup(entry_text);
8558 g_free(ainfo->content_type);
8559 ainfo->content_type = cnttype;
8562 g_free(ainfo->file);
8566 ainfo->size = (goffset)size;
8568 /* update tree store */
8569 text = to_human_readable(ainfo->size);
8570 gtk_tree_model_get_iter(model, &iter, path);
8571 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8572 COL_MIMETYPE, ainfo->content_type,
8574 COL_NAME, ainfo->name,
8580 gtk_tree_path_free(path);
8583 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8585 label = gtk_label_new(str); \
8586 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8587 GTK_FILL, 0, 0, 0); \
8588 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8590 entry = gtk_entry_new(); \
8591 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8592 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8595 static void compose_attach_property_create(gboolean *cancelled)
8601 GtkWidget *mimetype_entry;
8604 GtkListStore *optmenu_menu;
8605 GtkWidget *path_entry;
8606 GtkWidget *filename_entry;
8609 GtkWidget *cancel_btn;
8610 GList *mime_type_list, *strlist;
8613 debug_print("Creating attach_property window...\n");
8615 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8616 gtk_widget_set_size_request(window, 480, -1);
8617 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8618 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8619 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8620 g_signal_connect(G_OBJECT(window), "delete_event",
8621 G_CALLBACK(attach_property_delete_event),
8623 g_signal_connect(G_OBJECT(window), "key_press_event",
8624 G_CALLBACK(attach_property_key_pressed),
8627 vbox = gtk_vbox_new(FALSE, 8);
8628 gtk_container_add(GTK_CONTAINER(window), vbox);
8630 table = gtk_table_new(4, 2, FALSE);
8631 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8632 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8633 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8635 label = gtk_label_new(_("MIME type"));
8636 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8638 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8639 mimetype_entry = gtk_combo_box_entry_new_text();
8640 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8641 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8643 /* stuff with list */
8644 mime_type_list = procmime_get_mime_type_list();
8646 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8647 MimeType *type = (MimeType *) mime_type_list->data;
8650 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8652 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8655 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8656 (GCompareFunc)strcmp2);
8659 for (mime_type_list = strlist; mime_type_list != NULL;
8660 mime_type_list = mime_type_list->next) {
8661 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8662 g_free(mime_type_list->data);
8664 g_list_free(strlist);
8665 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8666 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8668 label = gtk_label_new(_("Encoding"));
8669 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8671 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8673 hbox = gtk_hbox_new(FALSE, 0);
8674 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8675 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8677 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8678 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8680 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8681 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8682 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8683 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8684 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8686 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8688 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8689 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8691 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8692 &ok_btn, GTK_STOCK_OK,
8694 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8695 gtk_widget_grab_default(ok_btn);
8697 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8698 G_CALLBACK(attach_property_ok),
8700 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8701 G_CALLBACK(attach_property_cancel),
8704 gtk_widget_show_all(vbox);
8706 attach_prop.window = window;
8707 attach_prop.mimetype_entry = mimetype_entry;
8708 attach_prop.encoding_optmenu = optmenu;
8709 attach_prop.path_entry = path_entry;
8710 attach_prop.filename_entry = filename_entry;
8711 attach_prop.ok_btn = ok_btn;
8712 attach_prop.cancel_btn = cancel_btn;
8715 #undef SET_LABEL_AND_ENTRY
8717 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8723 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8729 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8730 gboolean *cancelled)
8738 static gboolean attach_property_key_pressed(GtkWidget *widget,
8740 gboolean *cancelled)
8742 if (event && event->keyval == GDK_Escape) {
8746 if (event && event->keyval == GDK_Return) {
8754 static void compose_exec_ext_editor(Compose *compose)
8761 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8762 G_DIR_SEPARATOR, compose);
8764 if (pipe(pipe_fds) < 0) {
8770 if ((pid = fork()) < 0) {
8777 /* close the write side of the pipe */
8780 compose->exteditor_file = g_strdup(tmp);
8781 compose->exteditor_pid = pid;
8783 compose_set_ext_editor_sensitive(compose, FALSE);
8786 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8788 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8790 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8794 } else { /* process-monitoring process */
8800 /* close the read side of the pipe */
8803 if (compose_write_body_to_file(compose, tmp) < 0) {
8804 fd_write_all(pipe_fds[1], "2\n", 2);
8808 pid_ed = compose_exec_ext_editor_real(tmp);
8810 fd_write_all(pipe_fds[1], "1\n", 2);
8814 /* wait until editor is terminated */
8815 waitpid(pid_ed, NULL, 0);
8817 fd_write_all(pipe_fds[1], "0\n", 2);
8824 #endif /* G_OS_UNIX */
8828 static gint compose_exec_ext_editor_real(const gchar *file)
8835 cm_return_val_if_fail(file != NULL, -1);
8837 if ((pid = fork()) < 0) {
8842 if (pid != 0) return pid;
8844 /* grandchild process */
8846 if (setpgid(0, getppid()))
8849 if (prefs_common_get_ext_editor_cmd() &&
8850 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8851 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8852 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8854 if (prefs_common_get_ext_editor_cmd())
8855 g_warning("External editor command-line is invalid: '%s'\n",
8856 prefs_common_get_ext_editor_cmd());
8857 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8860 cmdline = strsplit_with_quote(buf, " ", 1024);
8861 execvp(cmdline[0], cmdline);
8864 g_strfreev(cmdline);
8869 static gboolean compose_ext_editor_kill(Compose *compose)
8871 pid_t pgid = compose->exteditor_pid * -1;
8874 ret = kill(pgid, 0);
8876 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8880 msg = g_strdup_printf
8881 (_("The external editor is still working.\n"
8882 "Force terminating the process?\n"
8883 "process group id: %d"), -pgid);
8884 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8885 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8889 if (val == G_ALERTALTERNATE) {
8890 g_source_remove(compose->exteditor_tag);
8891 g_io_channel_shutdown(compose->exteditor_ch,
8893 g_io_channel_unref(compose->exteditor_ch);
8895 if (kill(pgid, SIGTERM) < 0) perror("kill");
8896 waitpid(compose->exteditor_pid, NULL, 0);
8898 g_warning("Terminated process group id: %d", -pgid);
8899 g_warning("Temporary file: %s",
8900 compose->exteditor_file);
8902 compose_set_ext_editor_sensitive(compose, TRUE);
8904 g_free(compose->exteditor_file);
8905 compose->exteditor_file = NULL;
8906 compose->exteditor_pid = -1;
8907 compose->exteditor_ch = NULL;
8908 compose->exteditor_tag = -1;
8916 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8920 Compose *compose = (Compose *)data;
8923 debug_print(_("Compose: input from monitoring process\n"));
8925 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8927 g_io_channel_shutdown(source, FALSE, NULL);
8928 g_io_channel_unref(source);
8930 waitpid(compose->exteditor_pid, NULL, 0);
8932 if (buf[0] == '0') { /* success */
8933 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8934 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8936 gtk_text_buffer_set_text(buffer, "", -1);
8937 compose_insert_file(compose, compose->exteditor_file);
8938 compose_changed_cb(NULL, compose);
8939 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8941 if (claws_unlink(compose->exteditor_file) < 0)
8942 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8943 } else if (buf[0] == '1') { /* failed */
8944 g_warning("Couldn't exec external editor\n");
8945 if (claws_unlink(compose->exteditor_file) < 0)
8946 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8947 } else if (buf[0] == '2') {
8948 g_warning("Couldn't write to file\n");
8949 } else if (buf[0] == '3') {
8950 g_warning("Pipe read failed\n");
8953 compose_set_ext_editor_sensitive(compose, TRUE);
8955 g_free(compose->exteditor_file);
8956 compose->exteditor_file = NULL;
8957 compose->exteditor_pid = -1;
8958 compose->exteditor_ch = NULL;
8959 compose->exteditor_tag = -1;
8964 static void compose_set_ext_editor_sensitive(Compose *compose,
8967 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8968 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8969 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8970 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8972 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8973 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8975 gtk_widget_set_sensitive(compose->text, sensitive);
8976 if (compose->toolbar->send_btn)
8977 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8978 if (compose->toolbar->sendl_btn)
8979 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8980 if (compose->toolbar->draft_btn)
8981 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8982 if (compose->toolbar->insert_btn)
8983 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8984 if (compose->toolbar->sig_btn)
8985 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8986 if (compose->toolbar->exteditor_btn)
8987 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8988 if (compose->toolbar->linewrap_current_btn)
8989 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8990 if (compose->toolbar->linewrap_all_btn)
8991 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8993 #endif /* G_OS_UNIX */
8996 * compose_undo_state_changed:
8998 * Change the sensivity of the menuentries undo and redo
9000 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9001 gint redo_state, gpointer data)
9003 Compose *compose = (Compose *)data;
9005 switch (undo_state) {
9006 case UNDO_STATE_TRUE:
9007 if (!undostruct->undo_state) {
9008 undostruct->undo_state = TRUE;
9009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9012 case UNDO_STATE_FALSE:
9013 if (undostruct->undo_state) {
9014 undostruct->undo_state = FALSE;
9015 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9018 case UNDO_STATE_UNCHANGED:
9020 case UNDO_STATE_REFRESH:
9021 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9024 g_warning("Undo state not recognized");
9028 switch (redo_state) {
9029 case UNDO_STATE_TRUE:
9030 if (!undostruct->redo_state) {
9031 undostruct->redo_state = TRUE;
9032 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9035 case UNDO_STATE_FALSE:
9036 if (undostruct->redo_state) {
9037 undostruct->redo_state = FALSE;
9038 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9041 case UNDO_STATE_UNCHANGED:
9043 case UNDO_STATE_REFRESH:
9044 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9047 g_warning("Redo state not recognized");
9052 /* callback functions */
9054 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9055 * includes "non-client" (windows-izm) in calculation, so this calculation
9056 * may not be accurate.
9058 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9059 GtkAllocation *allocation,
9060 GtkSHRuler *shruler)
9062 if (prefs_common.show_ruler) {
9063 gint char_width = 0, char_height = 0;
9064 gint line_width_in_chars;
9066 gtkut_get_font_size(GTK_WIDGET(widget),
9067 &char_width, &char_height);
9068 line_width_in_chars =
9069 (allocation->width - allocation->x) / char_width;
9071 /* got the maximum */
9072 gtk_ruler_set_range(GTK_RULER(shruler),
9073 0.0, line_width_in_chars, 0,
9074 /*line_width_in_chars*/ char_width);
9083 ComposePrefType type;
9084 gboolean entry_marked;
9087 static void account_activated(GtkComboBox *optmenu, gpointer data)
9089 Compose *compose = (Compose *)data;
9092 gchar *folderidentifier;
9093 gint account_id = 0;
9096 GSList *list, *saved_list = NULL;
9097 HeaderEntryState *state;
9098 GtkRcStyle *style = NULL;
9099 static GdkColor yellow;
9100 static gboolean color_set = FALSE;
9102 /* Get ID of active account in the combo box */
9103 menu = gtk_combo_box_get_model(optmenu);
9104 gtk_combo_box_get_active_iter(optmenu, &iter);
9105 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9107 ac = account_find_from_id(account_id);
9108 cm_return_if_fail(ac != NULL);
9110 if (ac != compose->account) {
9111 compose_select_account(compose, ac, FALSE);
9113 for (list = compose->header_list; list; list = list->next) {
9114 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9116 if (hentry->type == PREF_ACCOUNT || !list->next) {
9117 compose_destroy_headerentry(compose, hentry);
9121 state = g_malloc0(sizeof(HeaderEntryState));
9122 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9123 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9124 state->entry = gtk_editable_get_chars(
9125 GTK_EDITABLE(hentry->entry), 0, -1);
9126 state->type = hentry->type;
9129 gdk_color_parse("#f5f6be", &yellow);
9130 color_set = gdk_colormap_alloc_color(
9131 gdk_colormap_get_system(),
9132 &yellow, FALSE, TRUE);
9135 style = gtk_widget_get_modifier_style(hentry->entry);
9136 state->entry_marked = gdk_color_equal(&yellow,
9137 &style->base[GTK_STATE_NORMAL]);
9139 saved_list = g_slist_append(saved_list, state);
9140 compose_destroy_headerentry(compose, hentry);
9143 compose->header_last = NULL;
9144 g_slist_free(compose->header_list);
9145 compose->header_list = NULL;
9146 compose->header_nextrow = 1;
9147 compose_create_header_entry(compose);
9149 if (ac->set_autocc && ac->auto_cc)
9150 compose_entry_append(compose, ac->auto_cc,
9151 COMPOSE_CC, PREF_ACCOUNT);
9153 if (ac->set_autobcc && ac->auto_bcc)
9154 compose_entry_append(compose, ac->auto_bcc,
9155 COMPOSE_BCC, PREF_ACCOUNT);
9157 if (ac->set_autoreplyto && ac->auto_replyto)
9158 compose_entry_append(compose, ac->auto_replyto,
9159 COMPOSE_REPLYTO, PREF_ACCOUNT);
9161 for (list = saved_list; list; list = list->next) {
9162 state = (HeaderEntryState *) list->data;
9164 compose_add_header_entry(compose, state->header,
9165 state->entry, state->type);
9166 if (state->entry_marked)
9167 compose_entry_mark_default_to(compose, state->entry);
9169 g_free(state->header);
9170 g_free(state->entry);
9173 g_slist_free(saved_list);
9175 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9176 (ac->protocol == A_NNTP) ?
9177 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9180 /* Set message save folder */
9181 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9182 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9184 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9185 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9187 compose_set_save_to(compose, NULL);
9188 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9189 folderidentifier = folder_item_get_identifier(account_get_special_folder
9190 (compose->account, F_OUTBOX));
9191 compose_set_save_to(compose, folderidentifier);
9192 g_free(folderidentifier);
9196 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9197 GtkTreeViewColumn *column, Compose *compose)
9199 compose_attach_property(NULL, compose);
9202 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9205 Compose *compose = (Compose *)data;
9206 GtkTreeSelection *attach_selection;
9207 gint attach_nr_selected;
9209 if (!event) return FALSE;
9211 if (event->button == 3) {
9212 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9213 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9215 if (attach_nr_selected > 0)
9217 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9218 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9220 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9221 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9224 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9225 NULL, NULL, event->button, event->time);
9232 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9235 Compose *compose = (Compose *)data;
9237 if (!event) return FALSE;
9239 switch (event->keyval) {
9241 compose_attach_remove_selected(NULL, compose);
9247 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9249 toolbar_comp_set_sensitive(compose, allow);
9250 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9251 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9253 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9255 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9256 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9257 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9259 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9263 static void compose_send_cb(GtkAction *action, gpointer data)
9265 Compose *compose = (Compose *)data;
9267 if (prefs_common.work_offline &&
9268 !inc_offline_should_override(TRUE,
9269 _("Claws Mail needs network access in order "
9270 "to send this email.")))
9273 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9274 g_source_remove(compose->draft_timeout_tag);
9275 compose->draft_timeout_tag = -1;
9278 compose_send(compose);
9281 static void compose_send_later_cb(GtkAction *action, gpointer data)
9283 Compose *compose = (Compose *)data;
9287 compose_allow_user_actions(compose, FALSE);
9288 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9289 compose_allow_user_actions(compose, TRUE);
9293 compose_close(compose);
9294 } else if (val == -1) {
9295 alertpanel_error(_("Could not queue message."));
9296 } else if (val == -2) {
9297 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9298 } else if (val == -3) {
9299 if (privacy_peek_error())
9300 alertpanel_error(_("Could not queue message for sending:\n\n"
9301 "Signature failed: %s"), privacy_get_error());
9302 } else if (val == -4) {
9303 alertpanel_error(_("Could not queue message for sending:\n\n"
9304 "Charset conversion failed."));
9305 } else if (val == -5) {
9306 alertpanel_error(_("Could not queue message for sending:\n\n"
9307 "Couldn't get recipient encryption key."));
9308 } else if (val == -6) {
9311 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9314 #define DRAFTED_AT_EXIT "drafted_at_exit"
9315 static void compose_register_draft(MsgInfo *info)
9317 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9318 DRAFTED_AT_EXIT, NULL);
9319 FILE *fp = g_fopen(filepath, "ab");
9322 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9330 gboolean compose_draft (gpointer data, guint action)
9332 Compose *compose = (Compose *)data;
9336 MsgFlags flag = {0, 0};
9337 static gboolean lock = FALSE;
9338 MsgInfo *newmsginfo;
9340 gboolean target_locked = FALSE;
9341 gboolean err = FALSE;
9343 if (lock) return FALSE;
9345 if (compose->sending)
9348 draft = account_get_special_folder(compose->account, F_DRAFT);
9349 cm_return_val_if_fail(draft != NULL, FALSE);
9351 if (!g_mutex_trylock(compose->mutex)) {
9352 /* we don't want to lock the mutex once it's available,
9353 * because as the only other part of compose.c locking
9354 * it is compose_close - which means once unlocked,
9355 * the compose struct will be freed */
9356 debug_print("couldn't lock mutex, probably sending\n");
9362 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9363 G_DIR_SEPARATOR, compose);
9364 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9365 FILE_OP_ERROR(tmp, "fopen");
9369 /* chmod for security */
9370 if (change_file_mode_rw(fp, tmp) < 0) {
9371 FILE_OP_ERROR(tmp, "chmod");
9372 g_warning("can't change file mode\n");
9375 /* Save draft infos */
9376 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9377 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9379 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9380 gchar *savefolderid;
9382 savefolderid = compose_get_save_to(compose);
9383 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9384 g_free(savefolderid);
9386 if (compose->return_receipt) {
9387 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9389 if (compose->privacy_system) {
9390 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9391 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9392 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9395 /* Message-ID of message replying to */
9396 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9399 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9400 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9403 /* Message-ID of message forwarding to */
9404 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9407 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9408 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9412 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9413 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9415 /* end of headers */
9416 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9423 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9427 if (fclose(fp) == EOF) {
9431 if (compose->targetinfo) {
9432 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9433 flag.perm_flags = target_locked?MSG_LOCKED:0;
9435 flag.tmp_flags = MSG_DRAFT;
9437 folder_item_scan(draft);
9438 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9439 MsgInfo *tmpinfo = NULL;
9440 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9441 if (compose->msgid) {
9442 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9445 msgnum = tmpinfo->msgnum;
9446 procmsg_msginfo_free(tmpinfo);
9447 debug_print("got draft msgnum %d from scanning\n", msgnum);
9449 debug_print("didn't get draft msgnum after scanning\n");
9452 debug_print("got draft msgnum %d from adding\n", msgnum);
9458 if (action != COMPOSE_AUTO_SAVE) {
9459 if (action != COMPOSE_DRAFT_FOR_EXIT)
9460 alertpanel_error(_("Could not save draft."));
9463 gtkut_window_popup(compose->window);
9464 val = alertpanel_full(_("Could not save draft"),
9465 _("Could not save draft.\n"
9466 "Do you want to cancel exit or discard this email?"),
9467 _("_Cancel exit"), _("_Discard email"), NULL,
9468 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9469 if (val == G_ALERTALTERNATE) {
9471 g_mutex_unlock(compose->mutex); /* must be done before closing */
9472 compose_close(compose);
9476 g_mutex_unlock(compose->mutex); /* must be done before closing */
9485 if (compose->mode == COMPOSE_REEDIT) {
9486 compose_remove_reedit_target(compose, TRUE);
9489 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9492 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9494 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9496 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9497 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9498 procmsg_msginfo_set_flags(newmsginfo, 0,
9499 MSG_HAS_ATTACHMENT);
9501 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9502 compose_register_draft(newmsginfo);
9504 procmsg_msginfo_free(newmsginfo);
9507 folder_item_scan(draft);
9509 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9511 g_mutex_unlock(compose->mutex); /* must be done before closing */
9512 compose_close(compose);
9518 path = folder_item_fetch_msg(draft, msgnum);
9520 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9523 if (g_stat(path, &s) < 0) {
9524 FILE_OP_ERROR(path, "stat");
9530 procmsg_msginfo_free(compose->targetinfo);
9531 compose->targetinfo = procmsg_msginfo_new();
9532 compose->targetinfo->msgnum = msgnum;
9533 compose->targetinfo->size = (goffset)s.st_size;
9534 compose->targetinfo->mtime = s.st_mtime;
9535 compose->targetinfo->folder = draft;
9537 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9538 compose->mode = COMPOSE_REEDIT;
9540 if (action == COMPOSE_AUTO_SAVE) {
9541 compose->autosaved_draft = compose->targetinfo;
9543 compose->modified = FALSE;
9544 compose_set_title(compose);
9548 g_mutex_unlock(compose->mutex);
9552 void compose_clear_exit_drafts(void)
9554 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9555 DRAFTED_AT_EXIT, NULL);
9556 if (is_file_exist(filepath))
9557 claws_unlink(filepath);
9562 void compose_reopen_exit_drafts(void)
9564 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9565 DRAFTED_AT_EXIT, NULL);
9566 FILE *fp = g_fopen(filepath, "rb");
9570 while (fgets(buf, sizeof(buf), fp)) {
9571 gchar **parts = g_strsplit(buf, "\t", 2);
9572 const gchar *folder = parts[0];
9573 int msgnum = parts[1] ? atoi(parts[1]):-1;
9575 if (folder && *folder && msgnum > -1) {
9576 FolderItem *item = folder_find_item_from_identifier(folder);
9577 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9579 compose_reedit(info, FALSE);
9586 compose_clear_exit_drafts();
9589 static void compose_save_cb(GtkAction *action, gpointer data)
9591 Compose *compose = (Compose *)data;
9592 compose_draft(compose, COMPOSE_KEEP_EDITING);
9593 compose->rmode = COMPOSE_REEDIT;
9596 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9598 if (compose && file_list) {
9601 for ( tmp = file_list; tmp; tmp = tmp->next) {
9602 gchar *file = (gchar *) tmp->data;
9603 gchar *utf8_filename = conv_filename_to_utf8(file);
9604 compose_attach_append(compose, file, utf8_filename, NULL);
9605 compose_changed_cb(NULL, compose);
9610 g_free(utf8_filename);
9615 static void compose_attach_cb(GtkAction *action, gpointer data)
9617 Compose *compose = (Compose *)data;
9620 if (compose->redirect_filename != NULL)
9623 file_list = filesel_select_multiple_files_open(_("Select file"));
9626 compose_attach_from_list(compose, file_list, TRUE);
9627 g_list_free(file_list);
9631 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9633 Compose *compose = (Compose *)data;
9635 gint files_inserted = 0;
9637 file_list = filesel_select_multiple_files_open(_("Select file"));
9642 for ( tmp = file_list; tmp; tmp = tmp->next) {
9643 gchar *file = (gchar *) tmp->data;
9644 gchar *filedup = g_strdup(file);
9645 gchar *shortfile = g_path_get_basename(filedup);
9646 ComposeInsertResult res;
9647 /* insert the file if the file is short or if the user confirmed that
9648 he/she wants to insert the large file */
9649 res = compose_insert_file(compose, file);
9650 if (res == COMPOSE_INSERT_READ_ERROR) {
9651 alertpanel_error(_("File '%s' could not be read."), shortfile);
9652 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9653 alertpanel_error(_("File '%s' contained invalid characters\n"
9654 "for the current encoding, insertion may be incorrect."),
9656 } else if (res == COMPOSE_INSERT_SUCCESS)
9663 g_list_free(file_list);
9667 if (files_inserted > 0 && compose->gtkaspell &&
9668 compose->gtkaspell->check_while_typing)
9669 gtkaspell_highlight_all(compose->gtkaspell);
9673 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9675 Compose *compose = (Compose *)data;
9677 compose_insert_sig(compose, FALSE);
9680 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9684 Compose *compose = (Compose *)data;
9686 gtkut_widget_get_uposition(widget, &x, &y);
9687 if (!compose->batch) {
9688 prefs_common.compose_x = x;
9689 prefs_common.compose_y = y;
9691 if (compose->sending || compose->updating)
9693 compose_close_cb(NULL, compose);
9697 void compose_close_toolbar(Compose *compose)
9699 compose_close_cb(NULL, compose);
9702 static void compose_close_cb(GtkAction *action, gpointer data)
9704 Compose *compose = (Compose *)data;
9708 if (compose->exteditor_tag != -1) {
9709 if (!compose_ext_editor_kill(compose))
9714 if (compose->modified) {
9715 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9716 if (!g_mutex_trylock(compose->mutex)) {
9717 /* we don't want to lock the mutex once it's available,
9718 * because as the only other part of compose.c locking
9719 * it is compose_close - which means once unlocked,
9720 * the compose struct will be freed */
9721 debug_print("couldn't lock mutex, probably sending\n");
9725 val = alertpanel(_("Discard message"),
9726 _("This message has been modified. Discard it?"),
9727 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9729 val = alertpanel(_("Save changes"),
9730 _("This message has been modified. Save the latest changes?"),
9731 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9733 g_mutex_unlock(compose->mutex);
9735 case G_ALERTDEFAULT:
9736 if (prefs_common.autosave && !reedit)
9737 compose_remove_draft(compose);
9739 case G_ALERTALTERNATE:
9740 compose_draft(data, COMPOSE_QUIT_EDITING);
9747 compose_close(compose);
9750 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9752 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9753 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9754 Compose *compose = (Compose *) data;
9757 compose->out_encoding = (CharSet)value;
9760 static void compose_address_cb(GtkAction *action, gpointer data)
9762 Compose *compose = (Compose *)data;
9764 addressbook_open(compose);
9767 static void about_show_cb(GtkAction *action, gpointer data)
9772 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9774 Compose *compose = (Compose *)data;
9779 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9780 cm_return_if_fail(tmpl != NULL);
9782 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9784 val = alertpanel(_("Apply template"), msg,
9785 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9788 if (val == G_ALERTDEFAULT)
9789 compose_template_apply(compose, tmpl, TRUE);
9790 else if (val == G_ALERTALTERNATE)
9791 compose_template_apply(compose, tmpl, FALSE);
9794 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9796 Compose *compose = (Compose *)data;
9798 compose_exec_ext_editor(compose);
9801 static void compose_undo_cb(GtkAction *action, gpointer data)
9803 Compose *compose = (Compose *)data;
9804 gboolean prev_autowrap = compose->autowrap;
9806 compose->autowrap = FALSE;
9807 undo_undo(compose->undostruct);
9808 compose->autowrap = prev_autowrap;
9811 static void compose_redo_cb(GtkAction *action, gpointer data)
9813 Compose *compose = (Compose *)data;
9814 gboolean prev_autowrap = compose->autowrap;
9816 compose->autowrap = FALSE;
9817 undo_redo(compose->undostruct);
9818 compose->autowrap = prev_autowrap;
9821 static void entry_cut_clipboard(GtkWidget *entry)
9823 if (GTK_IS_EDITABLE(entry))
9824 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9825 else if (GTK_IS_TEXT_VIEW(entry))
9826 gtk_text_buffer_cut_clipboard(
9827 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9828 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9832 static void entry_copy_clipboard(GtkWidget *entry)
9834 if (GTK_IS_EDITABLE(entry))
9835 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9836 else if (GTK_IS_TEXT_VIEW(entry))
9837 gtk_text_buffer_copy_clipboard(
9838 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9839 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9842 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9843 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9845 if (GTK_IS_TEXT_VIEW(entry)) {
9846 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9847 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9848 GtkTextIter start_iter, end_iter;
9850 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9852 if (contents == NULL)
9855 /* we shouldn't delete the selection when middle-click-pasting, or we
9856 * can't mid-click-paste our own selection */
9857 if (clip != GDK_SELECTION_PRIMARY) {
9858 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9859 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9862 if (insert_place == NULL) {
9863 /* if insert_place isn't specified, insert at the cursor.
9864 * used for Ctrl-V pasting */
9865 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9866 start = gtk_text_iter_get_offset(&start_iter);
9867 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9869 /* if insert_place is specified, paste here.
9870 * used for mid-click-pasting */
9871 start = gtk_text_iter_get_offset(insert_place);
9872 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9873 if (prefs_common.primary_paste_unselects)
9874 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9878 /* paste unwrapped: mark the paste so it's not wrapped later */
9879 end = start + strlen(contents);
9880 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9881 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9882 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9883 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9884 /* rewrap paragraph now (after a mid-click-paste) */
9885 mark_start = gtk_text_buffer_get_insert(buffer);
9886 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9887 gtk_text_iter_backward_char(&start_iter);
9888 compose_beautify_paragraph(compose, &start_iter, TRUE);
9890 } else if (GTK_IS_EDITABLE(entry))
9891 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9893 compose->modified = TRUE;
9896 static void entry_allsel(GtkWidget *entry)
9898 if (GTK_IS_EDITABLE(entry))
9899 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9900 else if (GTK_IS_TEXT_VIEW(entry)) {
9901 GtkTextIter startiter, enditer;
9902 GtkTextBuffer *textbuf;
9904 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9905 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9906 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9908 gtk_text_buffer_move_mark_by_name(textbuf,
9909 "selection_bound", &startiter);
9910 gtk_text_buffer_move_mark_by_name(textbuf,
9911 "insert", &enditer);
9915 static void compose_cut_cb(GtkAction *action, gpointer data)
9917 Compose *compose = (Compose *)data;
9918 if (compose->focused_editable
9919 #ifndef GENERIC_UMPC
9920 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9923 entry_cut_clipboard(compose->focused_editable);
9926 static void compose_copy_cb(GtkAction *action, gpointer data)
9928 Compose *compose = (Compose *)data;
9929 if (compose->focused_editable
9930 #ifndef GENERIC_UMPC
9931 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9934 entry_copy_clipboard(compose->focused_editable);
9937 static void compose_paste_cb(GtkAction *action, gpointer data)
9939 Compose *compose = (Compose *)data;
9941 GtkTextBuffer *buffer;
9943 if (compose->focused_editable &&
9944 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9945 entry_paste_clipboard(compose, compose->focused_editable,
9946 prefs_common.linewrap_pastes,
9947 GDK_SELECTION_CLIPBOARD, NULL);
9951 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9952 compose->gtkaspell &&
9953 compose->gtkaspell->check_while_typing)
9954 gtkaspell_highlight_all(compose->gtkaspell);
9958 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9960 Compose *compose = (Compose *)data;
9961 gint wrap_quote = prefs_common.linewrap_quote;
9962 if (compose->focused_editable
9963 #ifndef GENERIC_UMPC
9964 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9967 /* let text_insert() (called directly or at a later time
9968 * after the gtk_editable_paste_clipboard) know that
9969 * text is to be inserted as a quotation. implemented
9970 * by using a simple refcount... */
9971 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9972 G_OBJECT(compose->focused_editable),
9973 "paste_as_quotation"));
9974 g_object_set_data(G_OBJECT(compose->focused_editable),
9975 "paste_as_quotation",
9976 GINT_TO_POINTER(paste_as_quotation + 1));
9977 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9978 entry_paste_clipboard(compose, compose->focused_editable,
9979 prefs_common.linewrap_pastes,
9980 GDK_SELECTION_CLIPBOARD, NULL);
9981 prefs_common.linewrap_quote = wrap_quote;
9985 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9987 Compose *compose = (Compose *)data;
9989 GtkTextBuffer *buffer;
9991 if (compose->focused_editable
9992 #ifndef GENERIC_UMPC
9993 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9996 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9997 GDK_SELECTION_CLIPBOARD, NULL);
10001 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10002 compose->gtkaspell &&
10003 compose->gtkaspell->check_while_typing)
10004 gtkaspell_highlight_all(compose->gtkaspell);
10008 static void compose_paste_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 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10019 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10020 GDK_SELECTION_CLIPBOARD, NULL);
10024 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10025 compose->gtkaspell &&
10026 compose->gtkaspell->check_while_typing)
10027 gtkaspell_highlight_all(compose->gtkaspell);
10031 static void compose_allsel_cb(GtkAction *action, gpointer data)
10033 Compose *compose = (Compose *)data;
10034 if (compose->focused_editable
10035 #ifndef GENERIC_UMPC
10036 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10039 entry_allsel(compose->focused_editable);
10042 static void textview_move_beginning_of_line (GtkTextView *text)
10044 GtkTextBuffer *buffer;
10048 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10050 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10051 mark = gtk_text_buffer_get_insert(buffer);
10052 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10053 gtk_text_iter_set_line_offset(&ins, 0);
10054 gtk_text_buffer_place_cursor(buffer, &ins);
10057 static void textview_move_forward_character (GtkTextView *text)
10059 GtkTextBuffer *buffer;
10063 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10065 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10066 mark = gtk_text_buffer_get_insert(buffer);
10067 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10068 if (gtk_text_iter_forward_cursor_position(&ins))
10069 gtk_text_buffer_place_cursor(buffer, &ins);
10072 static void textview_move_backward_character (GtkTextView *text)
10074 GtkTextBuffer *buffer;
10078 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10080 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10081 mark = gtk_text_buffer_get_insert(buffer);
10082 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10083 if (gtk_text_iter_backward_cursor_position(&ins))
10084 gtk_text_buffer_place_cursor(buffer, &ins);
10087 static void textview_move_forward_word (GtkTextView *text)
10089 GtkTextBuffer *buffer;
10094 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10096 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10097 mark = gtk_text_buffer_get_insert(buffer);
10098 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10099 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10100 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10101 gtk_text_iter_backward_word_start(&ins);
10102 gtk_text_buffer_place_cursor(buffer, &ins);
10106 static void textview_move_backward_word (GtkTextView *text)
10108 GtkTextBuffer *buffer;
10113 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10115 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10116 mark = gtk_text_buffer_get_insert(buffer);
10117 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10118 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10119 if (gtk_text_iter_backward_word_starts(&ins, 1))
10120 gtk_text_buffer_place_cursor(buffer, &ins);
10123 static void textview_move_end_of_line (GtkTextView *text)
10125 GtkTextBuffer *buffer;
10129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10132 mark = gtk_text_buffer_get_insert(buffer);
10133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10134 if (gtk_text_iter_forward_to_line_end(&ins))
10135 gtk_text_buffer_place_cursor(buffer, &ins);
10138 static void textview_move_next_line (GtkTextView *text)
10140 GtkTextBuffer *buffer;
10145 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10147 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10148 mark = gtk_text_buffer_get_insert(buffer);
10149 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10150 offset = gtk_text_iter_get_line_offset(&ins);
10151 if (gtk_text_iter_forward_line(&ins)) {
10152 gtk_text_iter_set_line_offset(&ins, offset);
10153 gtk_text_buffer_place_cursor(buffer, &ins);
10157 static void textview_move_previous_line (GtkTextView *text)
10159 GtkTextBuffer *buffer;
10164 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10166 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10167 mark = gtk_text_buffer_get_insert(buffer);
10168 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10169 offset = gtk_text_iter_get_line_offset(&ins);
10170 if (gtk_text_iter_backward_line(&ins)) {
10171 gtk_text_iter_set_line_offset(&ins, offset);
10172 gtk_text_buffer_place_cursor(buffer, &ins);
10176 static void textview_delete_forward_character (GtkTextView *text)
10178 GtkTextBuffer *buffer;
10180 GtkTextIter ins, end_iter;
10182 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10184 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10185 mark = gtk_text_buffer_get_insert(buffer);
10186 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10188 if (gtk_text_iter_forward_char(&end_iter)) {
10189 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10193 static void textview_delete_backward_character (GtkTextView *text)
10195 GtkTextBuffer *buffer;
10197 GtkTextIter ins, end_iter;
10199 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10201 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10202 mark = gtk_text_buffer_get_insert(buffer);
10203 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10205 if (gtk_text_iter_backward_char(&end_iter)) {
10206 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10210 static void textview_delete_forward_word (GtkTextView *text)
10212 GtkTextBuffer *buffer;
10214 GtkTextIter ins, end_iter;
10216 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10218 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10219 mark = gtk_text_buffer_get_insert(buffer);
10220 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10222 if (gtk_text_iter_forward_word_end(&end_iter)) {
10223 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10227 static void textview_delete_backward_word (GtkTextView *text)
10229 GtkTextBuffer *buffer;
10231 GtkTextIter ins, end_iter;
10233 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10235 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10236 mark = gtk_text_buffer_get_insert(buffer);
10237 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10239 if (gtk_text_iter_backward_word_start(&end_iter)) {
10240 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10244 static void textview_delete_line (GtkTextView *text)
10246 GtkTextBuffer *buffer;
10248 GtkTextIter ins, start_iter, end_iter;
10250 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10252 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10253 mark = gtk_text_buffer_get_insert(buffer);
10254 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10257 gtk_text_iter_set_line_offset(&start_iter, 0);
10260 if (gtk_text_iter_ends_line(&end_iter)){
10261 if (!gtk_text_iter_forward_char(&end_iter))
10262 gtk_text_iter_backward_char(&start_iter);
10265 gtk_text_iter_forward_to_line_end(&end_iter);
10266 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10269 static void textview_delete_to_line_end (GtkTextView *text)
10271 GtkTextBuffer *buffer;
10273 GtkTextIter ins, end_iter;
10275 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10277 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10278 mark = gtk_text_buffer_get_insert(buffer);
10279 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10281 if (gtk_text_iter_ends_line(&end_iter))
10282 gtk_text_iter_forward_char(&end_iter);
10284 gtk_text_iter_forward_to_line_end(&end_iter);
10285 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10288 #define DO_ACTION(name, act) { \
10289 if(!strcmp(name, a_name)) { \
10293 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10295 const gchar *a_name = gtk_action_get_name(action);
10296 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10297 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10298 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10299 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10300 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10301 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10302 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10303 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10304 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10305 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10306 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10307 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10308 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10309 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10313 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10315 Compose *compose = (Compose *)data;
10316 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10317 ComposeCallAdvancedAction action = -1;
10319 action = compose_call_advanced_action_from_path(gaction);
10322 void (*do_action) (GtkTextView *text);
10323 } action_table[] = {
10324 {textview_move_beginning_of_line},
10325 {textview_move_forward_character},
10326 {textview_move_backward_character},
10327 {textview_move_forward_word},
10328 {textview_move_backward_word},
10329 {textview_move_end_of_line},
10330 {textview_move_next_line},
10331 {textview_move_previous_line},
10332 {textview_delete_forward_character},
10333 {textview_delete_backward_character},
10334 {textview_delete_forward_word},
10335 {textview_delete_backward_word},
10336 {textview_delete_line},
10337 {textview_delete_to_line_end}
10340 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10342 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10343 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10344 if (action_table[action].do_action)
10345 action_table[action].do_action(text);
10347 g_warning("Not implemented yet.");
10351 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10355 if (GTK_IS_EDITABLE(widget)) {
10356 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10357 gtk_editable_set_position(GTK_EDITABLE(widget),
10360 if (widget->parent && widget->parent->parent
10361 && widget->parent->parent->parent) {
10362 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10363 gint y = widget->allocation.y;
10364 gint height = widget->allocation.height;
10365 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10366 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10368 if (y < (int)shown->value) {
10369 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10371 if (y + height > (int)shown->value + (int)shown->page_size) {
10372 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10373 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10374 y + height - (int)shown->page_size - 1);
10376 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10377 (int)shown->upper - (int)shown->page_size - 1);
10384 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10385 compose->focused_editable = widget;
10387 #ifdef GENERIC_UMPC
10388 if (GTK_IS_TEXT_VIEW(widget)
10389 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10390 g_object_ref(compose->notebook);
10391 g_object_ref(compose->edit_vbox);
10392 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10393 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10394 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10395 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10396 g_object_unref(compose->notebook);
10397 g_object_unref(compose->edit_vbox);
10398 g_signal_handlers_block_by_func(G_OBJECT(widget),
10399 G_CALLBACK(compose_grab_focus_cb),
10401 gtk_widget_grab_focus(widget);
10402 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10403 G_CALLBACK(compose_grab_focus_cb),
10405 } else if (!GTK_IS_TEXT_VIEW(widget)
10406 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10407 g_object_ref(compose->notebook);
10408 g_object_ref(compose->edit_vbox);
10409 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10410 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10411 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10412 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10413 g_object_unref(compose->notebook);
10414 g_object_unref(compose->edit_vbox);
10415 g_signal_handlers_block_by_func(G_OBJECT(widget),
10416 G_CALLBACK(compose_grab_focus_cb),
10418 gtk_widget_grab_focus(widget);
10419 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10420 G_CALLBACK(compose_grab_focus_cb),
10426 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10428 compose->modified = TRUE;
10429 // compose_beautify_paragraph(compose, NULL, TRUE);
10430 #ifndef GENERIC_UMPC
10431 compose_set_title(compose);
10435 static void compose_wrap_cb(GtkAction *action, gpointer data)
10437 Compose *compose = (Compose *)data;
10438 compose_beautify_paragraph(compose, NULL, TRUE);
10441 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10443 Compose *compose = (Compose *)data;
10444 compose_wrap_all_full(compose, TRUE);
10447 static void compose_find_cb(GtkAction *action, gpointer data)
10449 Compose *compose = (Compose *)data;
10451 message_search_compose(compose);
10454 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10457 Compose *compose = (Compose *)data;
10458 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10459 if (compose->autowrap)
10460 compose_wrap_all_full(compose, TRUE);
10461 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10464 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10467 Compose *compose = (Compose *)data;
10468 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10471 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10473 Compose *compose = (Compose *)data;
10475 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10478 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10480 Compose *compose = (Compose *)data;
10482 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10485 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10487 g_free(compose->privacy_system);
10489 compose->privacy_system = g_strdup(account->default_privacy_system);
10490 compose_update_privacy_system_menu_item(compose, warn);
10493 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10495 Compose *compose = (Compose *)data;
10497 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10498 gtk_widget_show(compose->ruler_hbox);
10499 prefs_common.show_ruler = TRUE;
10501 gtk_widget_hide(compose->ruler_hbox);
10502 gtk_widget_queue_resize(compose->edit_vbox);
10503 prefs_common.show_ruler = FALSE;
10507 static void compose_attach_drag_received_cb (GtkWidget *widget,
10508 GdkDragContext *context,
10511 GtkSelectionData *data,
10514 gpointer user_data)
10516 Compose *compose = (Compose *)user_data;
10519 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10521 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10523 ) && gtk_drag_get_source_widget(context) !=
10524 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10525 list = uri_list_extract_filenames((const gchar *)data->data);
10526 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10527 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10528 compose_attach_append
10529 (compose, (const gchar *)tmp->data,
10530 utf8_filename, NULL);
10531 g_free(utf8_filename);
10533 if (list) compose_changed_cb(NULL, compose);
10534 list_free_strings(list);
10536 } else if (gtk_drag_get_source_widget(context)
10537 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10538 /* comes from our summaryview */
10539 SummaryView * summaryview = NULL;
10540 GSList * list = NULL, *cur = NULL;
10542 if (mainwindow_get_mainwindow())
10543 summaryview = mainwindow_get_mainwindow()->summaryview;
10546 list = summary_get_selected_msg_list(summaryview);
10548 for (cur = list; cur; cur = cur->next) {
10549 MsgInfo *msginfo = (MsgInfo *)cur->data;
10550 gchar *file = NULL;
10552 file = procmsg_get_message_file_full(msginfo,
10555 compose_attach_append(compose, (const gchar *)file,
10556 (const gchar *)file, "message/rfc822");
10560 g_slist_free(list);
10564 static gboolean compose_drag_drop(GtkWidget *widget,
10565 GdkDragContext *drag_context,
10567 guint time, gpointer user_data)
10569 /* not handling this signal makes compose_insert_drag_received_cb
10574 static void compose_insert_drag_received_cb (GtkWidget *widget,
10575 GdkDragContext *drag_context,
10578 GtkSelectionData *data,
10581 gpointer user_data)
10583 Compose *compose = (Compose *)user_data;
10586 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10589 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10591 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10593 AlertValue val = G_ALERTDEFAULT;
10595 list = uri_list_extract_filenames((const gchar *)data->data);
10596 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10597 /* Assume a list of no files, and data has ://, is a remote link */
10598 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10599 gchar *tmpfile = get_tmp_file();
10600 str_write_to_file(tmpdata, tmpfile);
10602 compose_insert_file(compose, tmpfile);
10603 claws_unlink(tmpfile);
10605 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10606 compose_beautify_paragraph(compose, NULL, TRUE);
10609 switch (prefs_common.compose_dnd_mode) {
10610 case COMPOSE_DND_ASK:
10611 val = alertpanel_full(_("Insert or attach?"),
10612 _("Do you want to insert the contents of the file(s) "
10613 "into the message body, or attach it to the email?"),
10614 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10615 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10617 case COMPOSE_DND_INSERT:
10618 val = G_ALERTALTERNATE;
10620 case COMPOSE_DND_ATTACH:
10621 val = G_ALERTOTHER;
10624 /* unexpected case */
10625 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10628 if (val & G_ALERTDISABLE) {
10629 val &= ~G_ALERTDISABLE;
10630 /* remember what action to perform by default, only if we don't click Cancel */
10631 if (val == G_ALERTALTERNATE)
10632 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10633 else if (val == G_ALERTOTHER)
10634 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10637 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10638 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10639 list_free_strings(list);
10642 } else if (val == G_ALERTOTHER) {
10643 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10644 list_free_strings(list);
10649 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10650 compose_insert_file(compose, (const gchar *)tmp->data);
10652 list_free_strings(list);
10654 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10659 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10662 static void compose_header_drag_received_cb (GtkWidget *widget,
10663 GdkDragContext *drag_context,
10666 GtkSelectionData *data,
10669 gpointer user_data)
10671 GtkEditable *entry = (GtkEditable *)user_data;
10672 gchar *email = (gchar *)data->data;
10674 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10677 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10678 gchar *decoded=g_new(gchar, strlen(email));
10681 email += strlen("mailto:");
10682 decode_uri(decoded, email); /* will fit */
10683 gtk_editable_delete_text(entry, 0, -1);
10684 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10685 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10689 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10692 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10694 Compose *compose = (Compose *)data;
10696 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10697 compose->return_receipt = TRUE;
10699 compose->return_receipt = FALSE;
10702 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10704 Compose *compose = (Compose *)data;
10706 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10707 compose->remove_references = TRUE;
10709 compose->remove_references = FALSE;
10712 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10713 ComposeHeaderEntry *headerentry)
10715 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10719 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10720 GdkEventKey *event,
10721 ComposeHeaderEntry *headerentry)
10723 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10724 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10725 !(event->state & GDK_MODIFIER_MASK) &&
10726 (event->keyval == GDK_BackSpace) &&
10727 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10728 gtk_container_remove
10729 (GTK_CONTAINER(headerentry->compose->header_table),
10730 headerentry->combo);
10731 gtk_container_remove
10732 (GTK_CONTAINER(headerentry->compose->header_table),
10733 headerentry->entry);
10734 headerentry->compose->header_list =
10735 g_slist_remove(headerentry->compose->header_list,
10737 g_free(headerentry);
10738 } else if (event->keyval == GDK_Tab) {
10739 if (headerentry->compose->header_last == headerentry) {
10740 /* Override default next focus, and give it to subject_entry
10741 * instead of notebook tabs
10743 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10744 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10751 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10752 ComposeHeaderEntry *headerentry)
10754 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10755 compose_create_header_entry(headerentry->compose);
10756 g_signal_handlers_disconnect_matched
10757 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10758 0, 0, NULL, NULL, headerentry);
10760 /* Automatically scroll down */
10761 GTK_EVENTS_FLUSH();
10762 compose_show_first_last_header(headerentry->compose, FALSE);
10768 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10770 GtkAdjustment *vadj;
10772 cm_return_if_fail(compose);
10773 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10774 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10775 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10776 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10777 gtk_adjustment_changed(vadj);
10780 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10781 const gchar *text, gint len, Compose *compose)
10783 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10784 (G_OBJECT(compose->text), "paste_as_quotation"));
10787 cm_return_if_fail(text != NULL);
10789 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10790 G_CALLBACK(text_inserted),
10792 if (paste_as_quotation) {
10794 const gchar *qmark;
10796 GtkTextIter start_iter;
10799 len = strlen(text);
10801 new_text = g_strndup(text, len);
10803 qmark = compose_quote_char_from_context(compose);
10805 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10806 gtk_text_buffer_place_cursor(buffer, iter);
10808 pos = gtk_text_iter_get_offset(iter);
10810 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10811 _("Quote format error at line %d."));
10812 quote_fmt_reset_vartable();
10814 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10815 GINT_TO_POINTER(paste_as_quotation - 1));
10817 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10818 gtk_text_buffer_place_cursor(buffer, iter);
10819 gtk_text_buffer_delete_mark(buffer, mark);
10821 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10822 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10823 compose_beautify_paragraph(compose, &start_iter, FALSE);
10824 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10825 gtk_text_buffer_delete_mark(buffer, mark);
10827 if (strcmp(text, "\n") || compose->automatic_break
10828 || gtk_text_iter_starts_line(iter)) {
10829 GtkTextIter before_ins;
10830 gtk_text_buffer_insert(buffer, iter, text, len);
10831 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10832 before_ins = *iter;
10833 gtk_text_iter_backward_chars(&before_ins, len);
10834 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10837 /* check if the preceding is just whitespace or quote */
10838 GtkTextIter start_line;
10839 gchar *tmp = NULL, *quote = NULL;
10840 gint quote_len = 0, is_normal = 0;
10841 start_line = *iter;
10842 gtk_text_iter_set_line_offset(&start_line, 0);
10843 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10846 if (*tmp == '\0') {
10849 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10857 gtk_text_buffer_insert(buffer, iter, text, len);
10859 gtk_text_buffer_insert_with_tags_by_name(buffer,
10860 iter, text, len, "no_join", NULL);
10865 if (!paste_as_quotation) {
10866 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10867 compose_beautify_paragraph(compose, iter, FALSE);
10868 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10869 gtk_text_buffer_delete_mark(buffer, mark);
10872 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10873 G_CALLBACK(text_inserted),
10875 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10877 if (prefs_common.autosave &&
10878 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10879 compose->draft_timeout_tag != -2 /* disabled while loading */)
10880 compose->draft_timeout_tag = g_timeout_add
10881 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10883 static gint compose_defer_auto_save_draft(Compose *compose)
10885 compose->draft_timeout_tag = -1;
10886 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10891 static void compose_check_all(GtkAction *action, gpointer data)
10893 Compose *compose = (Compose *)data;
10894 if (!compose->gtkaspell)
10897 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10898 claws_spell_entry_check_all(
10899 CLAWS_SPELL_ENTRY(compose->subject_entry));
10901 gtkaspell_check_all(compose->gtkaspell);
10904 static void compose_highlight_all(GtkAction *action, gpointer data)
10906 Compose *compose = (Compose *)data;
10907 if (compose->gtkaspell) {
10908 claws_spell_entry_recheck_all(
10909 CLAWS_SPELL_ENTRY(compose->subject_entry));
10910 gtkaspell_highlight_all(compose->gtkaspell);
10914 static void compose_check_backwards(GtkAction *action, gpointer data)
10916 Compose *compose = (Compose *)data;
10917 if (!compose->gtkaspell) {
10918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10922 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10923 claws_spell_entry_check_backwards(
10924 CLAWS_SPELL_ENTRY(compose->subject_entry));
10926 gtkaspell_check_backwards(compose->gtkaspell);
10929 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10931 Compose *compose = (Compose *)data;
10932 if (!compose->gtkaspell) {
10933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10937 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10938 claws_spell_entry_check_forwards_go(
10939 CLAWS_SPELL_ENTRY(compose->subject_entry));
10941 gtkaspell_check_forwards_go(compose->gtkaspell);
10946 *\brief Guess originating forward account from MsgInfo and several
10947 * "common preference" settings. Return NULL if no guess.
10949 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10951 PrefsAccount *account = NULL;
10953 cm_return_val_if_fail(msginfo, NULL);
10954 cm_return_val_if_fail(msginfo->folder, NULL);
10955 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10957 if (msginfo->folder->prefs->enable_default_account)
10958 account = account_find_from_id(msginfo->folder->prefs->default_account);
10961 account = msginfo->folder->folder->account;
10963 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10965 Xstrdup_a(to, msginfo->to, return NULL);
10966 extract_address(to);
10967 account = account_find_from_address(to, FALSE);
10970 if (!account && prefs_common.forward_account_autosel) {
10971 gchar cc[BUFFSIZE];
10972 if (!procheader_get_header_from_msginfo
10973 (msginfo, cc,sizeof cc , "Cc:")) {
10974 gchar *buf = cc + strlen("Cc:");
10975 extract_address(buf);
10976 account = account_find_from_address(buf, FALSE);
10980 if (!account && prefs_common.forward_account_autosel) {
10981 gchar deliveredto[BUFFSIZE];
10982 if (!procheader_get_header_from_msginfo
10983 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10984 gchar *buf = deliveredto + strlen("Delivered-To:");
10985 extract_address(buf);
10986 account = account_find_from_address(buf, FALSE);
10993 gboolean compose_close(Compose *compose)
10997 if (!g_mutex_trylock(compose->mutex)) {
10998 /* we have to wait for the (possibly deferred by auto-save)
10999 * drafting to be done, before destroying the compose under
11001 debug_print("waiting for drafting to finish...\n");
11002 compose_allow_user_actions(compose, FALSE);
11003 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11006 cm_return_val_if_fail(compose, FALSE);
11007 gtkut_widget_get_uposition(compose->window, &x, &y);
11008 if (!compose->batch) {
11009 prefs_common.compose_x = x;
11010 prefs_common.compose_y = y;
11012 g_mutex_unlock(compose->mutex);
11013 compose_destroy(compose);
11018 * Add entry field for each address in list.
11019 * \param compose E-Mail composition object.
11020 * \param listAddress List of (formatted) E-Mail addresses.
11022 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11025 node = listAddress;
11027 addr = ( gchar * ) node->data;
11028 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11029 node = g_list_next( node );
11033 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11034 guint action, gboolean opening_multiple)
11036 gchar *body = NULL;
11037 GSList *new_msglist = NULL;
11038 MsgInfo *tmp_msginfo = NULL;
11039 gboolean originally_enc = FALSE;
11040 gboolean originally_sig = FALSE;
11041 Compose *compose = NULL;
11042 gchar *s_system = NULL;
11044 cm_return_if_fail(msgview != NULL);
11046 cm_return_if_fail(msginfo_list != NULL);
11048 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11049 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11050 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11052 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11053 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11054 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11055 orig_msginfo, mimeinfo);
11056 if (tmp_msginfo != NULL) {
11057 new_msglist = g_slist_append(NULL, tmp_msginfo);
11059 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11060 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11061 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11063 tmp_msginfo->folder = orig_msginfo->folder;
11064 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11065 if (orig_msginfo->tags) {
11066 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11067 tmp_msginfo->folder->tags_dirty = TRUE;
11073 if (!opening_multiple)
11074 body = messageview_get_selection(msgview);
11077 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11078 procmsg_msginfo_free(tmp_msginfo);
11079 g_slist_free(new_msglist);
11081 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11083 if (compose && originally_enc) {
11084 compose_force_encryption(compose, compose->account, FALSE, s_system);
11087 if (compose && originally_sig && compose->account->default_sign_reply) {
11088 compose_force_signing(compose, compose->account, s_system);
11092 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11095 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11098 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11099 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11100 GSList *cur = msginfo_list;
11101 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11102 "messages. Opening the windows "
11103 "could take some time. Do you "
11104 "want to continue?"),
11105 g_slist_length(msginfo_list));
11106 if (g_slist_length(msginfo_list) > 9
11107 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11108 != G_ALERTALTERNATE) {
11113 /* We'll open multiple compose windows */
11114 /* let the WM place the next windows */
11115 compose_force_window_origin = FALSE;
11116 for (; cur; cur = cur->next) {
11118 tmplist.data = cur->data;
11119 tmplist.next = NULL;
11120 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11122 compose_force_window_origin = TRUE;
11124 /* forwarding multiple mails as attachments is done via a
11125 * single compose window */
11126 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11130 void compose_set_position(Compose *compose, gint pos)
11132 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11134 gtkut_text_view_set_position(text, pos);
11137 gboolean compose_search_string(Compose *compose,
11138 const gchar *str, gboolean case_sens)
11140 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11142 return gtkut_text_view_search_string(text, str, case_sens);
11145 gboolean compose_search_string_backward(Compose *compose,
11146 const gchar *str, gboolean case_sens)
11148 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11150 return gtkut_text_view_search_string_backward(text, str, case_sens);
11153 /* allocate a msginfo structure and populate its data from a compose data structure */
11154 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11156 MsgInfo *newmsginfo;
11158 gchar buf[BUFFSIZE];
11160 cm_return_val_if_fail( compose != NULL, NULL );
11162 newmsginfo = procmsg_msginfo_new();
11165 get_rfc822_date(buf, sizeof(buf));
11166 newmsginfo->date = g_strdup(buf);
11169 if (compose->from_name) {
11170 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11171 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11175 if (compose->subject_entry)
11176 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11178 /* to, cc, reply-to, newsgroups */
11179 for (list = compose->header_list; list; list = list->next) {
11180 gchar *header = gtk_editable_get_chars(
11182 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11183 gchar *entry = gtk_editable_get_chars(
11184 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11186 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11187 if ( newmsginfo->to == NULL ) {
11188 newmsginfo->to = g_strdup(entry);
11189 } else if (entry && *entry) {
11190 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11191 g_free(newmsginfo->to);
11192 newmsginfo->to = tmp;
11195 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11196 if ( newmsginfo->cc == NULL ) {
11197 newmsginfo->cc = g_strdup(entry);
11198 } else if (entry && *entry) {
11199 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11200 g_free(newmsginfo->cc);
11201 newmsginfo->cc = tmp;
11204 if ( strcasecmp(header,
11205 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11206 if ( newmsginfo->newsgroups == NULL ) {
11207 newmsginfo->newsgroups = g_strdup(entry);
11208 } else if (entry && *entry) {
11209 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11210 g_free(newmsginfo->newsgroups);
11211 newmsginfo->newsgroups = tmp;
11219 /* other data is unset */
11225 /* update compose's dictionaries from folder dict settings */
11226 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11227 FolderItem *folder_item)
11229 cm_return_if_fail(compose != NULL);
11231 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11232 FolderItemPrefs *prefs = folder_item->prefs;
11234 if (prefs->enable_default_dictionary)
11235 gtkaspell_change_dict(compose->gtkaspell,
11236 prefs->default_dictionary, FALSE);
11237 if (folder_item->prefs->enable_default_alt_dictionary)
11238 gtkaspell_change_alt_dict(compose->gtkaspell,
11239 prefs->default_alt_dictionary);
11240 if (prefs->enable_default_dictionary
11241 || prefs->enable_default_alt_dictionary)
11242 compose_spell_menu_changed(compose);