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);
3286 SET_ADDRESS(COMPOSE_INREPLYTO, compose->inreplyto);
3288 compose_update_priority_menu_item(compose);
3289 compose_update_privacy_system_menu_item(compose, FALSE);
3290 compose_show_first_last_header(compose, TRUE);
3296 static void compose_insert_sig(Compose *compose, gboolean replace)
3298 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3299 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3301 GtkTextIter iter, iter_end;
3302 gint cur_pos, ins_pos;
3303 gboolean prev_autowrap;
3304 gboolean found = FALSE;
3305 gboolean exists = FALSE;
3307 cm_return_if_fail(compose->account != NULL);
3311 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3312 G_CALLBACK(compose_changed_cb),
3315 mark = gtk_text_buffer_get_insert(buffer);
3316 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3317 cur_pos = gtk_text_iter_get_offset (&iter);
3320 gtk_text_buffer_get_end_iter(buffer, &iter);
3322 exists = (compose->sig_str != NULL);
3325 GtkTextIter first_iter, start_iter, end_iter;
3327 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3329 if (!exists || compose->sig_str[0] == '\0')
3332 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3333 compose->signature_tag);
3336 /* include previous \n\n */
3337 gtk_text_iter_backward_chars(&first_iter, 1);
3338 start_iter = first_iter;
3339 end_iter = first_iter;
3341 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3342 compose->signature_tag);
3343 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3344 compose->signature_tag);
3346 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3352 g_free(compose->sig_str);
3353 compose->sig_str = account_get_signature_str(compose->account);
3355 cur_pos = gtk_text_iter_get_offset(&iter);
3357 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3358 g_free(compose->sig_str);
3359 compose->sig_str = NULL;
3361 if (compose->sig_inserted == FALSE)
3362 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3363 compose->sig_inserted = TRUE;
3365 cur_pos = gtk_text_iter_get_offset(&iter);
3366 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3368 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3369 gtk_text_iter_forward_chars(&iter, 1);
3370 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3371 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3373 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3374 cur_pos = gtk_text_buffer_get_char_count (buffer);
3377 /* put the cursor where it should be
3378 * either where the quote_fmt says, either where it was */
3379 if (compose->set_cursor_pos < 0)
3380 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3382 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3383 compose->set_cursor_pos);
3385 compose->set_cursor_pos = -1;
3386 gtk_text_buffer_place_cursor(buffer, &iter);
3387 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3388 G_CALLBACK(compose_changed_cb),
3394 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3397 GtkTextBuffer *buffer;
3400 const gchar *cur_encoding;
3401 gchar buf[BUFFSIZE];
3404 gboolean prev_autowrap;
3405 gboolean badtxt = FALSE;
3406 struct stat file_stat;
3409 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3411 /* get the size of the file we are about to insert */
3412 ret = g_stat(file, &file_stat);
3414 gchar *shortfile = g_path_get_basename(file);
3415 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3417 return COMPOSE_INSERT_NO_FILE;
3418 } else if (prefs_common.warn_large_insert == TRUE) {
3420 /* ask user for confirmation if the file is large */
3421 if (prefs_common.warn_large_insert_size < 0 ||
3422 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3426 msg = g_strdup_printf(_("You are about to insert a file of %s "
3427 "in the message body. Are you sure you want to do that?"),
3428 to_human_readable(file_stat.st_size));
3429 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3430 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3433 /* do we ask for confirmation next time? */
3434 if (aval & G_ALERTDISABLE) {
3435 /* no confirmation next time, disable feature in preferences */
3436 aval &= ~G_ALERTDISABLE;
3437 prefs_common.warn_large_insert = FALSE;
3440 /* abort file insertion if user canceled action */
3441 if (aval != G_ALERTALTERNATE) {
3442 return COMPOSE_INSERT_NO_FILE;
3448 if ((fp = g_fopen(file, "rb")) == NULL) {
3449 FILE_OP_ERROR(file, "fopen");
3450 return COMPOSE_INSERT_READ_ERROR;
3453 prev_autowrap = compose->autowrap;
3454 compose->autowrap = FALSE;
3456 text = GTK_TEXT_VIEW(compose->text);
3457 buffer = gtk_text_view_get_buffer(text);
3458 mark = gtk_text_buffer_get_insert(buffer);
3459 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3461 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3462 G_CALLBACK(text_inserted),
3465 cur_encoding = conv_get_locale_charset_str_no_utf8();
3467 while (fgets(buf, sizeof(buf), fp) != NULL) {
3470 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3471 str = g_strdup(buf);
3473 str = conv_codeset_strdup
3474 (buf, cur_encoding, CS_INTERNAL);
3477 /* strip <CR> if DOS/Windows file,
3478 replace <CR> with <LF> if Macintosh file. */
3481 if (len > 0 && str[len - 1] != '\n') {
3483 if (str[len] == '\r') str[len] = '\n';
3486 gtk_text_buffer_insert(buffer, &iter, str, -1);
3490 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3491 G_CALLBACK(text_inserted),
3493 compose->autowrap = prev_autowrap;
3494 if (compose->autowrap)
3495 compose_wrap_all(compose);
3500 return COMPOSE_INSERT_INVALID_CHARACTER;
3502 return COMPOSE_INSERT_SUCCESS;
3505 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3506 const gchar *filename,
3507 const gchar *content_type)
3515 GtkListStore *store;
3517 gboolean has_binary = FALSE;
3519 if (!is_file_exist(file)) {
3520 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3521 gboolean result = FALSE;
3522 if (file_from_uri && is_file_exist(file_from_uri)) {
3523 result = compose_attach_append(
3524 compose, file_from_uri,
3528 g_free(file_from_uri);
3531 alertpanel_error("File %s doesn't exist\n", filename);
3534 if ((size = get_file_size(file)) < 0) {
3535 alertpanel_error("Can't get file size of %s\n", filename);
3539 alertpanel_error(_("File %s is empty."), filename);
3542 if ((fp = g_fopen(file, "rb")) == NULL) {
3543 alertpanel_error(_("Can't read %s."), filename);
3548 ainfo = g_new0(AttachInfo, 1);
3549 auto_ainfo = g_auto_pointer_new_with_free
3550 (ainfo, (GFreeFunc) compose_attach_info_free);
3551 ainfo->file = g_strdup(file);
3554 ainfo->content_type = g_strdup(content_type);
3555 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3557 MsgFlags flags = {0, 0};
3559 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3560 ainfo->encoding = ENC_7BIT;
3562 ainfo->encoding = ENC_8BIT;
3564 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3565 if (msginfo && msginfo->subject)
3566 name = g_strdup(msginfo->subject);
3568 name = g_path_get_basename(filename ? filename : file);
3570 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3572 procmsg_msginfo_free(msginfo);
3574 if (!g_ascii_strncasecmp(content_type, "text", 4))
3575 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3577 ainfo->encoding = ENC_BASE64;
3578 name = g_path_get_basename(filename ? filename : file);
3579 ainfo->name = g_strdup(name);
3583 ainfo->content_type = procmime_get_mime_type(file);
3584 if (!ainfo->content_type) {
3585 ainfo->content_type =
3586 g_strdup("application/octet-stream");
3587 ainfo->encoding = ENC_BASE64;
3588 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3590 procmime_get_encoding_for_text_file(file, &has_binary);
3592 ainfo->encoding = ENC_BASE64;
3593 name = g_path_get_basename(filename ? filename : file);
3594 ainfo->name = g_strdup(name);
3598 if (ainfo->name != NULL
3599 && !strcmp(ainfo->name, ".")) {
3600 g_free(ainfo->name);
3604 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3605 g_free(ainfo->content_type);
3606 ainfo->content_type = g_strdup("application/octet-stream");
3609 ainfo->size = (goffset)size;
3610 size_text = to_human_readable((goffset)size);
3612 store = GTK_LIST_STORE(gtk_tree_view_get_model
3613 (GTK_TREE_VIEW(compose->attach_clist)));
3615 gtk_list_store_append(store, &iter);
3616 gtk_list_store_set(store, &iter,
3617 COL_MIMETYPE, ainfo->content_type,
3618 COL_SIZE, size_text,
3619 COL_NAME, ainfo->name,
3621 COL_AUTODATA, auto_ainfo,
3624 g_auto_pointer_free(auto_ainfo);
3625 compose_attach_update_label(compose);
3629 static void compose_use_signing(Compose *compose, gboolean use_signing)
3631 compose->use_signing = use_signing;
3632 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3635 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3637 compose->use_encryption = use_encryption;
3638 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3641 #define NEXT_PART_NOT_CHILD(info) \
3643 node = info->node; \
3644 while (node->children) \
3645 node = g_node_last_child(node); \
3646 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3649 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3653 MimeInfo *firsttext = NULL;
3654 MimeInfo *encrypted = NULL;
3657 const gchar *partname = NULL;
3659 mimeinfo = procmime_scan_message(msginfo);
3660 if (!mimeinfo) return;
3662 if (mimeinfo->node->children == NULL) {
3663 procmime_mimeinfo_free_all(mimeinfo);
3667 /* find first content part */
3668 child = (MimeInfo *) mimeinfo->node->children->data;
3669 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3670 child = (MimeInfo *)child->node->children->data;
3673 if (child->type == MIMETYPE_TEXT) {
3675 debug_print("First text part found\n");
3676 } else if (compose->mode == COMPOSE_REEDIT &&
3677 child->type == MIMETYPE_APPLICATION &&
3678 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3679 encrypted = (MimeInfo *)child->node->parent->data;
3682 child = (MimeInfo *) mimeinfo->node->children->data;
3683 while (child != NULL) {
3686 if (child == encrypted) {
3687 /* skip this part of tree */
3688 NEXT_PART_NOT_CHILD(child);
3692 if (child->type == MIMETYPE_MULTIPART) {
3693 /* get the actual content */
3694 child = procmime_mimeinfo_next(child);
3698 if (child == firsttext) {
3699 child = procmime_mimeinfo_next(child);
3703 outfile = procmime_get_tmp_file_name(child);
3704 if ((err = procmime_get_part(outfile, child)) < 0)
3705 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3707 gchar *content_type;
3709 content_type = procmime_get_content_type_str(child->type, child->subtype);
3711 /* if we meet a pgp signature, we don't attach it, but
3712 * we force signing. */
3713 if ((strcmp(content_type, "application/pgp-signature") &&
3714 strcmp(content_type, "application/pkcs7-signature") &&
3715 strcmp(content_type, "application/x-pkcs7-signature"))
3716 || compose->mode == COMPOSE_REDIRECT) {
3717 partname = procmime_mimeinfo_get_parameter(child, "filename");
3718 if (partname == NULL)
3719 partname = procmime_mimeinfo_get_parameter(child, "name");
3720 if (partname == NULL)
3722 compose_attach_append(compose, outfile,
3723 partname, content_type);
3725 compose_force_signing(compose, compose->account, NULL);
3727 g_free(content_type);
3730 NEXT_PART_NOT_CHILD(child);
3732 procmime_mimeinfo_free_all(mimeinfo);
3735 #undef NEXT_PART_NOT_CHILD
3740 WAIT_FOR_INDENT_CHAR,
3741 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3744 /* return indent length, we allow:
3745 indent characters followed by indent characters or spaces/tabs,
3746 alphabets and numbers immediately followed by indent characters,
3747 and the repeating sequences of the above
3748 If quote ends with multiple spaces, only the first one is included. */
3749 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3750 const GtkTextIter *start, gint *len)
3752 GtkTextIter iter = *start;
3756 IndentState state = WAIT_FOR_INDENT_CHAR;
3759 gint alnum_count = 0;
3760 gint space_count = 0;
3763 if (prefs_common.quote_chars == NULL) {
3767 while (!gtk_text_iter_ends_line(&iter)) {
3768 wc = gtk_text_iter_get_char(&iter);
3769 if (g_unichar_iswide(wc))
3771 clen = g_unichar_to_utf8(wc, ch);
3775 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3776 is_space = g_unichar_isspace(wc);
3778 if (state == WAIT_FOR_INDENT_CHAR) {
3779 if (!is_indent && !g_unichar_isalnum(wc))
3782 quote_len += alnum_count + space_count + 1;
3783 alnum_count = space_count = 0;
3784 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3787 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3788 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3792 else if (is_indent) {
3793 quote_len += alnum_count + space_count + 1;
3794 alnum_count = space_count = 0;
3797 state = WAIT_FOR_INDENT_CHAR;
3801 gtk_text_iter_forward_char(&iter);
3804 if (quote_len > 0 && space_count > 0)
3810 if (quote_len > 0) {
3812 gtk_text_iter_forward_chars(&iter, quote_len);
3813 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3819 /* return >0 if the line is itemized */
3820 static int compose_itemized_length(GtkTextBuffer *buffer,
3821 const GtkTextIter *start)
3823 GtkTextIter iter = *start;
3828 if (gtk_text_iter_ends_line(&iter))
3833 wc = gtk_text_iter_get_char(&iter);
3834 if (!g_unichar_isspace(wc))
3836 gtk_text_iter_forward_char(&iter);
3837 if (gtk_text_iter_ends_line(&iter))
3841 clen = g_unichar_to_utf8(wc, ch);
3845 if (!strchr("*-+", ch[0]))
3848 gtk_text_iter_forward_char(&iter);
3849 if (gtk_text_iter_ends_line(&iter))
3851 wc = gtk_text_iter_get_char(&iter);
3852 if (g_unichar_isspace(wc)) {
3858 /* return the string at the start of the itemization */
3859 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3860 const GtkTextIter *start)
3862 GtkTextIter iter = *start;
3865 GString *item_chars = g_string_new("");
3868 if (gtk_text_iter_ends_line(&iter))
3873 wc = gtk_text_iter_get_char(&iter);
3874 if (!g_unichar_isspace(wc))
3876 gtk_text_iter_forward_char(&iter);
3877 if (gtk_text_iter_ends_line(&iter))
3879 g_string_append_unichar(item_chars, wc);
3882 str = item_chars->str;
3883 g_string_free(item_chars, FALSE);
3887 /* return the number of spaces at a line's start */
3888 static int compose_left_offset_length(GtkTextBuffer *buffer,
3889 const GtkTextIter *start)
3891 GtkTextIter iter = *start;
3894 if (gtk_text_iter_ends_line(&iter))
3898 wc = gtk_text_iter_get_char(&iter);
3899 if (!g_unichar_isspace(wc))
3902 gtk_text_iter_forward_char(&iter);
3903 if (gtk_text_iter_ends_line(&iter))
3907 gtk_text_iter_forward_char(&iter);
3908 if (gtk_text_iter_ends_line(&iter))
3913 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3914 const GtkTextIter *start,
3915 GtkTextIter *break_pos,
3919 GtkTextIter iter = *start, line_end = *start;
3920 PangoLogAttr *attrs;
3927 gboolean can_break = FALSE;
3928 gboolean do_break = FALSE;
3929 gboolean was_white = FALSE;
3930 gboolean prev_dont_break = FALSE;
3932 gtk_text_iter_forward_to_line_end(&line_end);
3933 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3934 len = g_utf8_strlen(str, -1);
3938 g_warning("compose_get_line_break_pos: len = 0!\n");
3942 /* g_print("breaking line: %d: %s (len = %d)\n",
3943 gtk_text_iter_get_line(&iter), str, len); */
3945 attrs = g_new(PangoLogAttr, len + 1);
3947 pango_default_break(str, -1, NULL, attrs, len + 1);
3951 /* skip quote and leading spaces */
3952 for (i = 0; *p != '\0' && i < len; i++) {
3955 wc = g_utf8_get_char(p);
3956 if (i >= quote_len && !g_unichar_isspace(wc))
3958 if (g_unichar_iswide(wc))
3960 else if (*p == '\t')
3964 p = g_utf8_next_char(p);
3967 for (; *p != '\0' && i < len; i++) {
3968 PangoLogAttr *attr = attrs + i;
3972 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3975 was_white = attr->is_white;
3977 /* don't wrap URI */
3978 if ((uri_len = get_uri_len(p)) > 0) {
3980 if (pos > 0 && col > max_col) {
3990 wc = g_utf8_get_char(p);
3991 if (g_unichar_iswide(wc)) {
3993 if (prev_dont_break && can_break && attr->is_line_break)
3995 } else if (*p == '\t')
3999 if (pos > 0 && col > max_col) {
4004 if (*p == '-' || *p == '/')
4005 prev_dont_break = TRUE;
4007 prev_dont_break = FALSE;
4009 p = g_utf8_next_char(p);
4013 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4018 *break_pos = *start;
4019 gtk_text_iter_set_line_offset(break_pos, pos);
4024 static gboolean compose_join_next_line(Compose *compose,
4025 GtkTextBuffer *buffer,
4027 const gchar *quote_str)
4029 GtkTextIter iter_ = *iter, cur, prev, next, end;
4030 PangoLogAttr attrs[3];
4032 gchar *next_quote_str;
4035 gboolean keep_cursor = FALSE;
4037 if (!gtk_text_iter_forward_line(&iter_) ||
4038 gtk_text_iter_ends_line(&iter_)) {
4041 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4043 if ((quote_str || next_quote_str) &&
4044 strcmp2(quote_str, next_quote_str) != 0) {
4045 g_free(next_quote_str);
4048 g_free(next_quote_str);
4051 if (quote_len > 0) {
4052 gtk_text_iter_forward_chars(&end, quote_len);
4053 if (gtk_text_iter_ends_line(&end)) {
4058 /* don't join itemized lines */
4059 if (compose_itemized_length(buffer, &end) > 0) {
4063 /* don't join signature separator */
4064 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4067 /* delete quote str */
4069 gtk_text_buffer_delete(buffer, &iter_, &end);
4071 /* don't join line breaks put by the user */
4073 gtk_text_iter_backward_char(&cur);
4074 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4075 gtk_text_iter_forward_char(&cur);
4079 gtk_text_iter_forward_char(&cur);
4080 /* delete linebreak and extra spaces */
4081 while (gtk_text_iter_backward_char(&cur)) {
4082 wc1 = gtk_text_iter_get_char(&cur);
4083 if (!g_unichar_isspace(wc1))
4088 while (!gtk_text_iter_ends_line(&cur)) {
4089 wc1 = gtk_text_iter_get_char(&cur);
4090 if (!g_unichar_isspace(wc1))
4092 gtk_text_iter_forward_char(&cur);
4095 if (!gtk_text_iter_equal(&prev, &next)) {
4098 mark = gtk_text_buffer_get_insert(buffer);
4099 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4100 if (gtk_text_iter_equal(&prev, &cur))
4102 gtk_text_buffer_delete(buffer, &prev, &next);
4106 /* insert space if required */
4107 gtk_text_iter_backward_char(&prev);
4108 wc1 = gtk_text_iter_get_char(&prev);
4109 wc2 = gtk_text_iter_get_char(&next);
4110 gtk_text_iter_forward_char(&next);
4111 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4112 pango_default_break(str, -1, NULL, attrs, 3);
4113 if (!attrs[1].is_line_break ||
4114 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4115 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4117 gtk_text_iter_backward_char(&iter_);
4118 gtk_text_buffer_place_cursor(buffer, &iter_);
4127 #define ADD_TXT_POS(bp_, ep_, pti_) \
4128 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4129 last = last->next; \
4130 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4131 last->next = NULL; \
4133 g_warning("alloc error scanning URIs\n"); \
4136 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4138 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4139 GtkTextBuffer *buffer;
4140 GtkTextIter iter, break_pos, end_of_line;
4141 gchar *quote_str = NULL;
4143 gboolean wrap_quote = prefs_common.linewrap_quote;
4144 gboolean prev_autowrap = compose->autowrap;
4145 gint startq_offset = -1, noq_offset = -1;
4146 gint uri_start = -1, uri_stop = -1;
4147 gint nouri_start = -1, nouri_stop = -1;
4148 gint num_blocks = 0;
4149 gint quotelevel = -1;
4150 gboolean modified = force;
4151 gboolean removed = FALSE;
4152 gboolean modified_before_remove = FALSE;
4154 gboolean start = TRUE;
4155 gint itemized_len = 0, rem_item_len = 0;
4156 gchar *itemized_chars = NULL;
4157 gboolean item_continuation = FALSE;
4162 if (compose->draft_timeout_tag == -2) {
4166 compose->autowrap = FALSE;
4168 buffer = gtk_text_view_get_buffer(text);
4169 undo_wrapping(compose->undostruct, TRUE);
4174 mark = gtk_text_buffer_get_insert(buffer);
4175 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4179 if (compose->draft_timeout_tag == -2) {
4180 if (gtk_text_iter_ends_line(&iter)) {
4181 while (gtk_text_iter_ends_line(&iter) &&
4182 gtk_text_iter_forward_line(&iter))
4185 while (gtk_text_iter_backward_line(&iter)) {
4186 if (gtk_text_iter_ends_line(&iter)) {
4187 gtk_text_iter_forward_line(&iter);
4193 /* move to line start */
4194 gtk_text_iter_set_line_offset(&iter, 0);
4197 itemized_len = compose_itemized_length(buffer, &iter);
4199 if (!itemized_len) {
4200 itemized_len = compose_left_offset_length(buffer, &iter);
4201 item_continuation = TRUE;
4205 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4207 /* go until paragraph end (empty line) */
4208 while (start || !gtk_text_iter_ends_line(&iter)) {
4209 gchar *scanpos = NULL;
4210 /* parse table - in order of priority */
4212 const gchar *needle; /* token */
4214 /* token search function */
4215 gchar *(*search) (const gchar *haystack,
4216 const gchar *needle);
4217 /* part parsing function */
4218 gboolean (*parse) (const gchar *start,
4219 const gchar *scanpos,
4223 /* part to URI function */
4224 gchar *(*build_uri) (const gchar *bp,
4228 static struct table parser[] = {
4229 {"http://", strcasestr, get_uri_part, make_uri_string},
4230 {"https://", strcasestr, get_uri_part, make_uri_string},
4231 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4232 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4233 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4234 {"www.", strcasestr, get_uri_part, make_http_string},
4235 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4236 {"@", strcasestr, get_email_part, make_email_string}
4238 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4239 gint last_index = PARSE_ELEMS;
4241 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4245 if (!prev_autowrap && num_blocks == 0) {
4247 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4248 G_CALLBACK(text_inserted),
4251 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4254 uri_start = uri_stop = -1;
4256 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4259 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4260 if (startq_offset == -1)
4261 startq_offset = gtk_text_iter_get_offset(&iter);
4262 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4263 if (quotelevel > 2) {
4264 /* recycle colors */
4265 if (prefs_common.recycle_quote_colors)
4274 if (startq_offset == -1)
4275 noq_offset = gtk_text_iter_get_offset(&iter);
4279 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4282 if (gtk_text_iter_ends_line(&iter)) {
4284 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4285 prefs_common.linewrap_len,
4287 GtkTextIter prev, next, cur;
4288 if (prev_autowrap != FALSE || force) {
4289 compose->automatic_break = TRUE;
4291 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4292 compose->automatic_break = FALSE;
4293 if (itemized_len && compose->autoindent) {
4294 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4295 if (!item_continuation)
4296 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4298 } else if (quote_str && wrap_quote) {
4299 compose->automatic_break = TRUE;
4301 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4302 compose->automatic_break = FALSE;
4303 if (itemized_len && compose->autoindent) {
4304 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4305 if (!item_continuation)
4306 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4310 /* remove trailing spaces */
4312 rem_item_len = itemized_len;
4313 while (compose->autoindent && rem_item_len-- > 0)
4314 gtk_text_iter_backward_char(&cur);
4315 gtk_text_iter_backward_char(&cur);
4318 while (!gtk_text_iter_starts_line(&cur)) {
4321 gtk_text_iter_backward_char(&cur);
4322 wc = gtk_text_iter_get_char(&cur);
4323 if (!g_unichar_isspace(wc))
4327 if (!gtk_text_iter_equal(&prev, &next)) {
4328 gtk_text_buffer_delete(buffer, &prev, &next);
4330 gtk_text_iter_forward_char(&break_pos);
4334 gtk_text_buffer_insert(buffer, &break_pos,
4338 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4340 /* move iter to current line start */
4341 gtk_text_iter_set_line_offset(&iter, 0);
4348 /* move iter to next line start */
4354 if (!prev_autowrap && num_blocks > 0) {
4356 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4357 G_CALLBACK(text_inserted),
4361 while (!gtk_text_iter_ends_line(&end_of_line)) {
4362 gtk_text_iter_forward_char(&end_of_line);
4364 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4366 nouri_start = gtk_text_iter_get_offset(&iter);
4367 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4369 walk_pos = gtk_text_iter_get_offset(&iter);
4370 /* FIXME: this looks phony. scanning for anything in the parse table */
4371 for (n = 0; n < PARSE_ELEMS; n++) {
4374 tmp = parser[n].search(walk, parser[n].needle);
4376 if (scanpos == NULL || tmp < scanpos) {
4385 /* check if URI can be parsed */
4386 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4387 (const gchar **)&ep, FALSE)
4388 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4392 strlen(parser[last_index].needle);
4395 uri_start = walk_pos + (bp - o_walk);
4396 uri_stop = walk_pos + (ep - o_walk);
4400 gtk_text_iter_forward_line(&iter);
4403 if (startq_offset != -1) {
4404 GtkTextIter startquote, endquote;
4405 gtk_text_buffer_get_iter_at_offset(
4406 buffer, &startquote, startq_offset);
4409 switch (quotelevel) {
4411 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4412 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4413 gtk_text_buffer_apply_tag_by_name(
4414 buffer, "quote0", &startquote, &endquote);
4415 gtk_text_buffer_remove_tag_by_name(
4416 buffer, "quote1", &startquote, &endquote);
4417 gtk_text_buffer_remove_tag_by_name(
4418 buffer, "quote2", &startquote, &endquote);
4423 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4424 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4425 gtk_text_buffer_apply_tag_by_name(
4426 buffer, "quote1", &startquote, &endquote);
4427 gtk_text_buffer_remove_tag_by_name(
4428 buffer, "quote0", &startquote, &endquote);
4429 gtk_text_buffer_remove_tag_by_name(
4430 buffer, "quote2", &startquote, &endquote);
4435 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4436 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4437 gtk_text_buffer_apply_tag_by_name(
4438 buffer, "quote2", &startquote, &endquote);
4439 gtk_text_buffer_remove_tag_by_name(
4440 buffer, "quote0", &startquote, &endquote);
4441 gtk_text_buffer_remove_tag_by_name(
4442 buffer, "quote1", &startquote, &endquote);
4448 } else if (noq_offset != -1) {
4449 GtkTextIter startnoquote, endnoquote;
4450 gtk_text_buffer_get_iter_at_offset(
4451 buffer, &startnoquote, noq_offset);
4454 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4455 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4456 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4457 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4458 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4459 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4460 gtk_text_buffer_remove_tag_by_name(
4461 buffer, "quote0", &startnoquote, &endnoquote);
4462 gtk_text_buffer_remove_tag_by_name(
4463 buffer, "quote1", &startnoquote, &endnoquote);
4464 gtk_text_buffer_remove_tag_by_name(
4465 buffer, "quote2", &startnoquote, &endnoquote);
4471 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4472 GtkTextIter nouri_start_iter, nouri_end_iter;
4473 gtk_text_buffer_get_iter_at_offset(
4474 buffer, &nouri_start_iter, nouri_start);
4475 gtk_text_buffer_get_iter_at_offset(
4476 buffer, &nouri_end_iter, nouri_stop);
4477 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4478 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4479 gtk_text_buffer_remove_tag_by_name(
4480 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4481 modified_before_remove = modified;
4486 if (uri_start >= 0 && uri_stop > 0) {
4487 GtkTextIter uri_start_iter, uri_end_iter, back;
4488 gtk_text_buffer_get_iter_at_offset(
4489 buffer, &uri_start_iter, uri_start);
4490 gtk_text_buffer_get_iter_at_offset(
4491 buffer, &uri_end_iter, uri_stop);
4492 back = uri_end_iter;
4493 gtk_text_iter_backward_char(&back);
4494 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4495 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4496 gtk_text_buffer_apply_tag_by_name(
4497 buffer, "link", &uri_start_iter, &uri_end_iter);
4499 if (removed && !modified_before_remove) {
4505 // debug_print("not modified, out after %d lines\n", lines);
4509 // debug_print("modified, out after %d lines\n", lines);
4511 g_free(itemized_chars);
4514 undo_wrapping(compose->undostruct, FALSE);
4515 compose->autowrap = prev_autowrap;
4520 void compose_action_cb(void *data)
4522 Compose *compose = (Compose *)data;
4523 compose_wrap_all(compose);
4526 static void compose_wrap_all(Compose *compose)
4528 compose_wrap_all_full(compose, FALSE);
4531 static void compose_wrap_all_full(Compose *compose, gboolean force)
4533 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4534 GtkTextBuffer *buffer;
4536 gboolean modified = TRUE;
4538 buffer = gtk_text_view_get_buffer(text);
4540 gtk_text_buffer_get_start_iter(buffer, &iter);
4541 while (!gtk_text_iter_is_end(&iter) && modified)
4542 modified = compose_beautify_paragraph(compose, &iter, force);
4546 static void compose_set_title(Compose *compose)
4552 edited = compose->modified ? _(" [Edited]") : "";
4554 subject = gtk_editable_get_chars(
4555 GTK_EDITABLE(compose->subject_entry), 0, -1);
4557 #ifndef GENERIC_UMPC
4558 if (subject && strlen(subject))
4559 str = g_strdup_printf(_("%s - Compose message%s"),
4562 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4564 str = g_strdup(_("Compose message"));
4567 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4573 * compose_current_mail_account:
4575 * Find a current mail account (the currently selected account, or the
4576 * default account, if a news account is currently selected). If a
4577 * mail account cannot be found, display an error message.
4579 * Return value: Mail account, or NULL if not found.
4581 static PrefsAccount *
4582 compose_current_mail_account(void)
4586 if (cur_account && cur_account->protocol != A_NNTP)
4589 ac = account_get_default();
4590 if (!ac || ac->protocol == A_NNTP) {
4591 alertpanel_error(_("Account for sending mail is not specified.\n"
4592 "Please select a mail account before sending."));
4599 #define QUOTE_IF_REQUIRED(out, str) \
4601 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4605 len = strlen(str) + 3; \
4606 if ((__tmp = alloca(len)) == NULL) { \
4607 g_warning("can't allocate memory\n"); \
4608 g_string_free(header, TRUE); \
4611 g_snprintf(__tmp, len, "\"%s\"", str); \
4616 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4617 g_warning("can't allocate memory\n"); \
4618 g_string_free(header, TRUE); \
4621 strcpy(__tmp, str); \
4627 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4629 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4633 len = strlen(str) + 3; \
4634 if ((__tmp = alloca(len)) == NULL) { \
4635 g_warning("can't allocate memory\n"); \
4638 g_snprintf(__tmp, len, "\"%s\"", str); \
4643 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4644 g_warning("can't allocate memory\n"); \
4647 strcpy(__tmp, str); \
4653 static void compose_select_account(Compose *compose, PrefsAccount *account,
4656 gchar *from = NULL, *header;
4657 ComposeHeaderEntry *header_entry;
4659 cm_return_if_fail(account != NULL);
4661 compose->account = account;
4662 if (account->name && *account->name) {
4664 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4665 from = g_strdup_printf("%s <%s>",
4666 buf, account->address);
4667 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4669 from = g_strdup_printf("<%s>",
4671 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4676 compose_set_title(compose);
4678 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4679 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4681 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4682 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4683 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4685 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4687 activate_privacy_system(compose, account, FALSE);
4689 if (!init && compose->mode != COMPOSE_REDIRECT) {
4690 undo_block(compose->undostruct);
4691 compose_insert_sig(compose, TRUE);
4692 undo_unblock(compose->undostruct);
4695 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4696 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4698 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4699 if (account->protocol == A_NNTP) {
4700 if (!strcmp(header, _("To:")))
4701 combobox_select_by_text(
4702 GTK_COMBO_BOX(header_entry->combo),
4705 if (!strcmp(header, _("Newsgroups:")))
4706 combobox_select_by_text(
4707 GTK_COMBO_BOX(header_entry->combo),
4715 /* use account's dict info if set */
4716 if (compose->gtkaspell) {
4717 if (account->enable_default_dictionary)
4718 gtkaspell_change_dict(compose->gtkaspell,
4719 account->default_dictionary, FALSE);
4720 if (account->enable_default_alt_dictionary)
4721 gtkaspell_change_alt_dict(compose->gtkaspell,
4722 account->default_alt_dictionary);
4723 if (account->enable_default_dictionary
4724 || account->enable_default_alt_dictionary)
4725 compose_spell_menu_changed(compose);
4730 gboolean compose_check_for_valid_recipient(Compose *compose) {
4731 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4732 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4733 gboolean recipient_found = FALSE;
4737 /* free to and newsgroup list */
4738 slist_free_strings(compose->to_list);
4739 g_slist_free(compose->to_list);
4740 compose->to_list = NULL;
4742 slist_free_strings(compose->newsgroup_list);
4743 g_slist_free(compose->newsgroup_list);
4744 compose->newsgroup_list = NULL;
4746 /* search header entries for to and newsgroup entries */
4747 for (list = compose->header_list; list; list = list->next) {
4750 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4751 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4754 if (entry[0] != '\0') {
4755 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4756 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4757 compose->to_list = address_list_append(compose->to_list, entry);
4758 recipient_found = TRUE;
4761 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4762 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4763 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4764 recipient_found = TRUE;
4771 return recipient_found;
4774 static gboolean compose_check_for_set_recipients(Compose *compose)
4776 if (compose->account->set_autocc && compose->account->auto_cc) {
4777 gboolean found_other = FALSE;
4779 /* search header entries for to and newsgroup entries */
4780 for (list = compose->header_list; list; list = list->next) {
4783 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4784 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4787 if (strcmp(entry, compose->account->auto_cc)
4788 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4798 if (compose->batch) {
4799 gtk_widget_show_all(compose->window);
4801 aval = alertpanel(_("Send"),
4802 _("The only recipient is the default CC address. Send anyway?"),
4803 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4804 if (aval != G_ALERTALTERNATE)
4808 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4809 gboolean found_other = FALSE;
4811 /* search header entries for to and newsgroup entries */
4812 for (list = compose->header_list; list; list = list->next) {
4815 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4816 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4819 if (strcmp(entry, compose->account->auto_bcc)
4820 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4830 if (compose->batch) {
4831 gtk_widget_show_all(compose->window);
4833 aval = alertpanel(_("Send"),
4834 _("The only recipient is the default BCC address. Send anyway?"),
4835 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4836 if (aval != G_ALERTALTERNATE)
4843 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4847 if (compose_check_for_valid_recipient(compose) == FALSE) {
4848 if (compose->batch) {
4849 gtk_widget_show_all(compose->window);
4851 alertpanel_error(_("Recipient is not specified."));
4855 if (compose_check_for_set_recipients(compose) == FALSE) {
4859 if (!compose->batch) {
4860 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4861 if (*str == '\0' && check_everything == TRUE &&
4862 compose->mode != COMPOSE_REDIRECT) {
4864 gchar *button_label;
4867 if (compose->sending)
4868 button_label = _("+_Send");
4870 button_label = _("+_Queue");
4871 message = g_strdup_printf(_("Subject is empty. %s"),
4872 compose->sending?_("Send it anyway?"):
4873 _("Queue it anyway?"));
4875 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4876 GTK_STOCK_CANCEL, button_label, NULL);
4878 if (aval != G_ALERTALTERNATE)
4883 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4889 gint compose_send(Compose *compose)
4892 FolderItem *folder = NULL;
4894 gchar *msgpath = NULL;
4895 gboolean discard_window = FALSE;
4896 gchar *errstr = NULL;
4897 gchar *tmsgid = NULL;
4898 MainWindow *mainwin = mainwindow_get_mainwindow();
4899 gboolean queued_removed = FALSE;
4901 if (prefs_common.send_dialog_invisible
4902 || compose->batch == TRUE)
4903 discard_window = TRUE;
4905 compose_allow_user_actions (compose, FALSE);
4906 compose->sending = TRUE;
4908 if (compose_check_entries(compose, TRUE) == FALSE) {
4909 if (compose->batch) {
4910 gtk_widget_show_all(compose->window);
4916 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4919 if (compose->batch) {
4920 gtk_widget_show_all(compose->window);
4923 alertpanel_error(_("Could not queue message for sending:\n\n"
4924 "Charset conversion failed."));
4925 } else if (val == -5) {
4926 alertpanel_error(_("Could not queue message for sending:\n\n"
4927 "Couldn't get recipient encryption key."));
4928 } else if (val == -6) {
4930 } else if (val == -3) {
4931 if (privacy_peek_error())
4932 alertpanel_error(_("Could not queue message for sending:\n\n"
4933 "Signature failed: %s"), privacy_get_error());
4934 } else if (val == -2 && errno != 0) {
4935 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4937 alertpanel_error(_("Could not queue message for sending."));
4942 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4943 if (discard_window) {
4944 compose->sending = FALSE;
4945 compose_close(compose);
4946 /* No more compose access in the normal codepath
4947 * after this point! */
4952 alertpanel_error(_("The message was queued but could not be "
4953 "sent.\nUse \"Send queued messages\" from "
4954 "the main window to retry."));
4955 if (!discard_window) {
4962 if (msgpath == NULL) {
4963 msgpath = folder_item_fetch_msg(folder, msgnum);
4964 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4967 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4968 claws_unlink(msgpath);
4971 if (!discard_window) {
4973 if (!queued_removed)
4974 folder_item_remove_msg(folder, msgnum);
4975 folder_item_scan(folder);
4977 /* make sure we delete that */
4978 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4980 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4981 folder_item_remove_msg(folder, tmp->msgnum);
4982 procmsg_msginfo_free(tmp);
4989 if (!queued_removed)
4990 folder_item_remove_msg(folder, msgnum);
4991 folder_item_scan(folder);
4993 /* make sure we delete that */
4994 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4996 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4997 folder_item_remove_msg(folder, tmp->msgnum);
4998 procmsg_msginfo_free(tmp);
5001 if (!discard_window) {
5002 compose->sending = FALSE;
5003 compose_allow_user_actions (compose, TRUE);
5004 compose_close(compose);
5008 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5009 "the main window to retry."), errstr);
5012 alertpanel_error_log(_("The message was queued but could not be "
5013 "sent.\nUse \"Send queued messages\" from "
5014 "the main window to retry."));
5016 if (!discard_window) {
5025 toolbar_main_set_sensitive(mainwin);
5026 main_window_set_menu_sensitive(mainwin);
5032 compose_allow_user_actions (compose, TRUE);
5033 compose->sending = FALSE;
5034 compose->modified = TRUE;
5035 toolbar_main_set_sensitive(mainwin);
5036 main_window_set_menu_sensitive(mainwin);
5041 static gboolean compose_use_attach(Compose *compose)
5043 GtkTreeModel *model = gtk_tree_view_get_model
5044 (GTK_TREE_VIEW(compose->attach_clist));
5045 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5048 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5051 gchar buf[BUFFSIZE];
5053 gboolean first_to_address;
5054 gboolean first_cc_address;
5056 ComposeHeaderEntry *headerentry;
5057 const gchar *headerentryname;
5058 const gchar *cc_hdr;
5059 const gchar *to_hdr;
5060 gboolean err = FALSE;
5062 debug_print("Writing redirect header\n");
5064 cc_hdr = prefs_common_translated_header_name("Cc:");
5065 to_hdr = prefs_common_translated_header_name("To:");
5067 first_to_address = TRUE;
5068 for (list = compose->header_list; list; list = list->next) {
5069 headerentry = ((ComposeHeaderEntry *)list->data);
5070 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5072 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5073 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5074 Xstrdup_a(str, entstr, return -1);
5076 if (str[0] != '\0') {
5077 compose_convert_header
5078 (compose, buf, sizeof(buf), str,
5079 strlen("Resent-To") + 2, TRUE);
5081 if (first_to_address) {
5082 err |= (fprintf(fp, "Resent-To: ") < 0);
5083 first_to_address = FALSE;
5085 err |= (fprintf(fp, ",") < 0);
5087 err |= (fprintf(fp, "%s", buf) < 0);
5091 if (!first_to_address) {
5092 err |= (fprintf(fp, "\n") < 0);
5095 first_cc_address = TRUE;
5096 for (list = compose->header_list; list; list = list->next) {
5097 headerentry = ((ComposeHeaderEntry *)list->data);
5098 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5100 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5101 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5102 Xstrdup_a(str, strg, return -1);
5104 if (str[0] != '\0') {
5105 compose_convert_header
5106 (compose, buf, sizeof(buf), str,
5107 strlen("Resent-Cc") + 2, TRUE);
5109 if (first_cc_address) {
5110 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5111 first_cc_address = FALSE;
5113 err |= (fprintf(fp, ",") < 0);
5115 err |= (fprintf(fp, "%s", buf) < 0);
5119 if (!first_cc_address) {
5120 err |= (fprintf(fp, "\n") < 0);
5123 return (err ? -1:0);
5126 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5128 gchar buf[BUFFSIZE];
5130 const gchar *entstr;
5131 /* struct utsname utsbuf; */
5132 gboolean err = FALSE;
5134 cm_return_val_if_fail(fp != NULL, -1);
5135 cm_return_val_if_fail(compose->account != NULL, -1);
5136 cm_return_val_if_fail(compose->account->address != NULL, -1);
5139 get_rfc822_date(buf, sizeof(buf));
5140 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5143 if (compose->account->name && *compose->account->name) {
5144 compose_convert_header
5145 (compose, buf, sizeof(buf), compose->account->name,
5146 strlen("From: "), TRUE);
5147 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5148 buf, compose->account->address) < 0);
5150 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5153 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5154 if (*entstr != '\0') {
5155 Xstrdup_a(str, entstr, return -1);
5158 compose_convert_header(compose, buf, sizeof(buf), str,
5159 strlen("Subject: "), FALSE);
5160 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5164 /* Resent-Message-ID */
5165 if (compose->account->set_domain && compose->account->domain) {
5166 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5167 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5168 g_snprintf(buf, sizeof(buf), "%s",
5169 strchr(compose->account->address, '@') ?
5170 strchr(compose->account->address, '@')+1 :
5171 compose->account->address);
5173 g_snprintf(buf, sizeof(buf), "%s", "");
5176 if (compose->account->gen_msgid) {
5178 if (compose->account->msgid_with_addr) {
5179 addr = compose->account->address;
5181 generate_msgid(buf, sizeof(buf), addr);
5182 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5183 compose->msgid = g_strdup(buf);
5185 compose->msgid = NULL;
5188 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5191 /* separator between header and body */
5192 err |= (fputs("\n", fp) == EOF);
5194 return (err ? -1:0);
5197 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5201 gchar buf[BUFFSIZE];
5203 gboolean skip = FALSE;
5204 gboolean err = FALSE;
5205 gchar *not_included[]={
5206 "Return-Path:", "Delivered-To:", "Received:",
5207 "Subject:", "X-UIDL:", "AF:",
5208 "NF:", "PS:", "SRH:",
5209 "SFN:", "DSR:", "MID:",
5210 "CFG:", "PT:", "S:",
5211 "RQ:", "SSV:", "NSV:",
5212 "SSH:", "R:", "MAID:",
5213 "NAID:", "RMID:", "FMID:",
5214 "SCF:", "RRCPT:", "NG:",
5215 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5216 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5217 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5218 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5219 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5222 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5223 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5227 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5229 for (i = 0; not_included[i] != NULL; i++) {
5230 if (g_ascii_strncasecmp(buf, not_included[i],
5231 strlen(not_included[i])) == 0) {
5238 if (fputs(buf, fdest) == -1)
5241 if (!prefs_common.redirect_keep_from) {
5242 if (g_ascii_strncasecmp(buf, "From:",
5243 strlen("From:")) == 0) {
5244 err |= (fputs(" (by way of ", fdest) == EOF);
5245 if (compose->account->name
5246 && *compose->account->name) {
5247 compose_convert_header
5248 (compose, buf, sizeof(buf),
5249 compose->account->name,
5252 err |= (fprintf(fdest, "%s <%s>",
5254 compose->account->address) < 0);
5256 err |= (fprintf(fdest, "%s",
5257 compose->account->address) < 0);
5258 err |= (fputs(")", fdest) == EOF);
5262 if (fputs("\n", fdest) == -1)
5269 if (compose_redirect_write_headers(compose, fdest))
5272 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5273 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5286 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5288 GtkTextBuffer *buffer;
5289 GtkTextIter start, end;
5292 const gchar *out_codeset;
5293 EncodingType encoding = ENC_UNKNOWN;
5294 MimeInfo *mimemsg, *mimetext;
5296 const gchar *src_codeset = CS_INTERNAL;
5297 gchar *from_addr = NULL;
5298 gchar *from_name = NULL;
5300 if (action == COMPOSE_WRITE_FOR_SEND)
5301 attach_parts = TRUE;
5303 /* create message MimeInfo */
5304 mimemsg = procmime_mimeinfo_new();
5305 mimemsg->type = MIMETYPE_MESSAGE;
5306 mimemsg->subtype = g_strdup("rfc822");
5307 mimemsg->content = MIMECONTENT_MEM;
5308 mimemsg->tmp = TRUE; /* must free content later */
5309 mimemsg->data.mem = compose_get_header(compose);
5311 /* Create text part MimeInfo */
5312 /* get all composed text */
5313 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5314 gtk_text_buffer_get_start_iter(buffer, &start);
5315 gtk_text_buffer_get_end_iter(buffer, &end);
5316 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5318 out_codeset = conv_get_charset_str(compose->out_encoding);
5320 if (!out_codeset && is_ascii_str(chars)) {
5321 out_codeset = CS_US_ASCII;
5322 } else if (prefs_common.outgoing_fallback_to_ascii &&
5323 is_ascii_str(chars)) {
5324 out_codeset = CS_US_ASCII;
5325 encoding = ENC_7BIT;
5329 gchar *test_conv_global_out = NULL;
5330 gchar *test_conv_reply = NULL;
5332 /* automatic mode. be automatic. */
5333 codeconv_set_strict(TRUE);
5335 out_codeset = conv_get_outgoing_charset_str();
5337 debug_print("trying to convert to %s\n", out_codeset);
5338 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5341 if (!test_conv_global_out && compose->orig_charset
5342 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5343 out_codeset = compose->orig_charset;
5344 debug_print("failure; trying to convert to %s\n", out_codeset);
5345 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5348 if (!test_conv_global_out && !test_conv_reply) {
5350 out_codeset = CS_INTERNAL;
5351 debug_print("failure; finally using %s\n", out_codeset);
5353 g_free(test_conv_global_out);
5354 g_free(test_conv_reply);
5355 codeconv_set_strict(FALSE);
5358 if (encoding == ENC_UNKNOWN) {
5359 if (prefs_common.encoding_method == CTE_BASE64)
5360 encoding = ENC_BASE64;
5361 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5362 encoding = ENC_QUOTED_PRINTABLE;
5363 else if (prefs_common.encoding_method == CTE_8BIT)
5364 encoding = ENC_8BIT;
5366 encoding = procmime_get_encoding_for_charset(out_codeset);
5369 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5370 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5372 if (action == COMPOSE_WRITE_FOR_SEND) {
5373 codeconv_set_strict(TRUE);
5374 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5375 codeconv_set_strict(FALSE);
5381 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5382 "to the specified %s charset.\n"
5383 "Send it as %s?"), out_codeset, src_codeset);
5384 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5385 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5388 if (aval != G_ALERTALTERNATE) {
5393 out_codeset = src_codeset;
5399 out_codeset = src_codeset;
5404 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5405 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5406 strstr(buf, "\nFrom ") != NULL) {
5407 encoding = ENC_QUOTED_PRINTABLE;
5411 mimetext = procmime_mimeinfo_new();
5412 mimetext->content = MIMECONTENT_MEM;
5413 mimetext->tmp = TRUE; /* must free content later */
5414 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5415 * and free the data, which we need later. */
5416 mimetext->data.mem = g_strdup(buf);
5417 mimetext->type = MIMETYPE_TEXT;
5418 mimetext->subtype = g_strdup("plain");
5419 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5420 g_strdup(out_codeset));
5422 /* protect trailing spaces when signing message */
5423 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5424 privacy_system_can_sign(compose->privacy_system)) {
5425 encoding = ENC_QUOTED_PRINTABLE;
5428 debug_print("main text: %zd bytes encoded as %s in %d\n",
5429 strlen(buf), out_codeset, encoding);
5431 /* check for line length limit */
5432 if (action == COMPOSE_WRITE_FOR_SEND &&
5433 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5434 check_line_length(buf, 1000, &line) < 0) {
5438 msg = g_strdup_printf
5439 (_("Line %d exceeds the line length limit (998 bytes).\n"
5440 "The contents of the message might be broken on the way to the delivery.\n"
5442 "Send it anyway?"), line + 1);
5443 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5445 if (aval != G_ALERTALTERNATE) {
5451 if (encoding != ENC_UNKNOWN)
5452 procmime_encode_content(mimetext, encoding);
5454 /* append attachment parts */
5455 if (compose_use_attach(compose) && attach_parts) {
5456 MimeInfo *mimempart;
5457 gchar *boundary = NULL;
5458 mimempart = procmime_mimeinfo_new();
5459 mimempart->content = MIMECONTENT_EMPTY;
5460 mimempart->type = MIMETYPE_MULTIPART;
5461 mimempart->subtype = g_strdup("mixed");
5465 boundary = generate_mime_boundary(NULL);
5466 } while (strstr(buf, boundary) != NULL);
5468 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5471 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5473 g_node_append(mimempart->node, mimetext->node);
5474 g_node_append(mimemsg->node, mimempart->node);
5476 if (compose_add_attachments(compose, mimempart) < 0)
5479 g_node_append(mimemsg->node, mimetext->node);
5483 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5484 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5485 /* extract name and address */
5486 if (strstr(spec, " <") && strstr(spec, ">")) {
5487 from_addr = g_strdup(strrchr(spec, '<')+1);
5488 *(strrchr(from_addr, '>')) = '\0';
5489 from_name = g_strdup(spec);
5490 *(strrchr(from_name, '<')) = '\0';
5497 /* sign message if sending */
5498 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5499 privacy_system_can_sign(compose->privacy_system))
5500 if (!privacy_sign(compose->privacy_system, mimemsg,
5501 compose->account, from_addr)) {
5508 procmime_write_mimeinfo(mimemsg, fp);
5510 procmime_mimeinfo_free_all(mimemsg);
5515 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5517 GtkTextBuffer *buffer;
5518 GtkTextIter start, end;
5523 if ((fp = g_fopen(file, "wb")) == NULL) {
5524 FILE_OP_ERROR(file, "fopen");
5528 /* chmod for security */
5529 if (change_file_mode_rw(fp, file) < 0) {
5530 FILE_OP_ERROR(file, "chmod");
5531 g_warning("can't change file mode\n");
5534 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5535 gtk_text_buffer_get_start_iter(buffer, &start);
5536 gtk_text_buffer_get_end_iter(buffer, &end);
5537 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5539 chars = conv_codeset_strdup
5540 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5543 if (!chars) return -1;
5546 len = strlen(chars);
5547 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5548 FILE_OP_ERROR(file, "fwrite");
5557 if (fclose(fp) == EOF) {
5558 FILE_OP_ERROR(file, "fclose");
5565 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5568 MsgInfo *msginfo = compose->targetinfo;
5570 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5571 if (!msginfo) return -1;
5573 if (!force && MSG_IS_LOCKED(msginfo->flags))
5576 item = msginfo->folder;
5577 cm_return_val_if_fail(item != NULL, -1);
5579 if (procmsg_msg_exist(msginfo) &&
5580 (folder_has_parent_of_type(item, F_QUEUE) ||
5581 folder_has_parent_of_type(item, F_DRAFT)
5582 || msginfo == compose->autosaved_draft)) {
5583 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5584 g_warning("can't remove the old message\n");
5587 debug_print("removed reedit target %d\n", msginfo->msgnum);
5594 static void compose_remove_draft(Compose *compose)
5597 MsgInfo *msginfo = compose->targetinfo;
5598 drafts = account_get_special_folder(compose->account, F_DRAFT);
5600 if (procmsg_msg_exist(msginfo)) {
5601 folder_item_remove_msg(drafts, msginfo->msgnum);
5606 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5607 gboolean remove_reedit_target)
5609 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5612 static gboolean compose_warn_encryption(Compose *compose)
5614 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5615 AlertValue val = G_ALERTALTERNATE;
5617 if (warning == NULL)
5620 val = alertpanel_full(_("Encryption warning"), warning,
5621 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5622 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5623 if (val & G_ALERTDISABLE) {
5624 val &= ~G_ALERTDISABLE;
5625 if (val == G_ALERTALTERNATE)
5626 privacy_inhibit_encrypt_warning(compose->privacy_system,
5630 if (val == G_ALERTALTERNATE) {
5637 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5638 gchar **msgpath, gboolean check_subject,
5639 gboolean remove_reedit_target)
5646 static gboolean lock = FALSE;
5647 PrefsAccount *mailac = NULL, *newsac = NULL;
5648 gboolean err = FALSE;
5650 debug_print("queueing message...\n");
5651 cm_return_val_if_fail(compose->account != NULL, -1);
5655 if (compose_check_entries(compose, check_subject) == FALSE) {
5657 if (compose->batch) {
5658 gtk_widget_show_all(compose->window);
5663 if (!compose->to_list && !compose->newsgroup_list) {
5664 g_warning("can't get recipient list.");
5669 if (compose->to_list) {
5670 if (compose->account->protocol != A_NNTP)
5671 mailac = compose->account;
5672 else if (cur_account && cur_account->protocol != A_NNTP)
5673 mailac = cur_account;
5674 else if (!(mailac = compose_current_mail_account())) {
5676 alertpanel_error(_("No account for sending mails available!"));
5681 if (compose->newsgroup_list) {
5682 if (compose->account->protocol == A_NNTP)
5683 newsac = compose->account;
5686 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5691 /* write queue header */
5692 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5693 G_DIR_SEPARATOR, compose, (guint) rand());
5694 debug_print("queuing to %s\n", tmp);
5695 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5696 FILE_OP_ERROR(tmp, "fopen");
5702 if (change_file_mode_rw(fp, tmp) < 0) {
5703 FILE_OP_ERROR(tmp, "chmod");
5704 g_warning("can't change file mode\n");
5707 /* queueing variables */
5708 err |= (fprintf(fp, "AF:\n") < 0);
5709 err |= (fprintf(fp, "NF:0\n") < 0);
5710 err |= (fprintf(fp, "PS:10\n") < 0);
5711 err |= (fprintf(fp, "SRH:1\n") < 0);
5712 err |= (fprintf(fp, "SFN:\n") < 0);
5713 err |= (fprintf(fp, "DSR:\n") < 0);
5715 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5717 err |= (fprintf(fp, "MID:\n") < 0);
5718 err |= (fprintf(fp, "CFG:\n") < 0);
5719 err |= (fprintf(fp, "PT:0\n") < 0);
5720 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5721 err |= (fprintf(fp, "RQ:\n") < 0);
5723 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5725 err |= (fprintf(fp, "SSV:\n") < 0);
5727 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5729 err |= (fprintf(fp, "NSV:\n") < 0);
5730 err |= (fprintf(fp, "SSH:\n") < 0);
5731 /* write recepient list */
5732 if (compose->to_list) {
5733 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5734 for (cur = compose->to_list->next; cur != NULL;
5736 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5737 err |= (fprintf(fp, "\n") < 0);
5739 /* write newsgroup list */
5740 if (compose->newsgroup_list) {
5741 err |= (fprintf(fp, "NG:") < 0);
5742 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5743 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5744 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5745 err |= (fprintf(fp, "\n") < 0);
5747 /* Sylpheed account IDs */
5749 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5751 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5754 if (compose->privacy_system != NULL) {
5755 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5756 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5757 if (compose->use_encryption) {
5759 if (!compose_warn_encryption(compose)) {
5766 if (mailac && mailac->encrypt_to_self) {
5767 GSList *tmp_list = g_slist_copy(compose->to_list);
5768 tmp_list = g_slist_append(tmp_list, compose->account->address);
5769 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5770 g_slist_free(tmp_list);
5772 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5774 if (encdata != NULL) {
5775 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5776 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5777 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5779 } /* else we finally dont want to encrypt */
5781 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5782 /* and if encdata was null, it means there's been a problem in
5794 /* Save copy folder */
5795 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5796 gchar *savefolderid;
5798 savefolderid = compose_get_save_to(compose);
5799 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5800 g_free(savefolderid);
5802 /* Save copy folder */
5803 if (compose->return_receipt) {
5804 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5806 /* Message-ID of message replying to */
5807 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5810 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5811 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5814 /* Message-ID of message forwarding to */
5815 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5818 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5819 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5823 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5824 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5826 /* end of headers */
5827 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5829 if (compose->redirect_filename != NULL) {
5830 if (compose_redirect_write_to_file(compose, fp) < 0) {
5839 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5844 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5848 g_warning("failed to write queue message\n");
5855 if (fclose(fp) == EOF) {
5856 FILE_OP_ERROR(tmp, "fclose");
5863 if (item && *item) {
5866 queue = account_get_special_folder(compose->account, F_QUEUE);
5869 g_warning("can't find queue folder\n");
5875 folder_item_scan(queue);
5876 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5877 g_warning("can't queue the message\n");
5884 if (msgpath == NULL) {
5890 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5891 compose_remove_reedit_target(compose, FALSE);
5894 if ((msgnum != NULL) && (item != NULL)) {
5902 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5905 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5907 struct stat statbuf;
5908 gchar *type, *subtype;
5909 GtkTreeModel *model;
5912 model = gtk_tree_view_get_model(tree_view);
5914 if (!gtk_tree_model_get_iter_first(model, &iter))
5917 gtk_tree_model_get(model, &iter,
5921 if (!is_file_exist(ainfo->file)) {
5922 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5923 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5924 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5926 if (val == G_ALERTDEFAULT) {
5931 mimepart = procmime_mimeinfo_new();
5932 mimepart->content = MIMECONTENT_FILE;
5933 mimepart->data.filename = g_strdup(ainfo->file);
5934 mimepart->tmp = FALSE; /* or we destroy our attachment */
5935 mimepart->offset = 0;
5937 g_stat(ainfo->file, &statbuf);
5938 mimepart->length = statbuf.st_size;
5940 type = g_strdup(ainfo->content_type);
5942 if (!strchr(type, '/')) {
5944 type = g_strdup("application/octet-stream");
5947 subtype = strchr(type, '/') + 1;
5948 *(subtype - 1) = '\0';
5949 mimepart->type = procmime_get_media_type(type);
5950 mimepart->subtype = g_strdup(subtype);
5953 if (mimepart->type == MIMETYPE_MESSAGE &&
5954 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5955 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5958 if (mimepart->type == MIMETYPE_APPLICATION &&
5959 !strcmp2(mimepart->subtype, "octet-stream"))
5960 g_hash_table_insert(mimepart->typeparameters,
5961 g_strdup("name"), g_strdup(ainfo->name));
5962 g_hash_table_insert(mimepart->dispositionparameters,
5963 g_strdup("filename"), g_strdup(ainfo->name));
5964 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5968 if (compose->use_signing) {
5969 if (ainfo->encoding == ENC_7BIT)
5970 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5971 else if (ainfo->encoding == ENC_8BIT)
5972 ainfo->encoding = ENC_BASE64;
5975 procmime_encode_content(mimepart, ainfo->encoding);
5977 g_node_append(parent->node, mimepart->node);
5978 } while (gtk_tree_model_iter_next(model, &iter));
5983 #define IS_IN_CUSTOM_HEADER(header) \
5984 (compose->account->add_customhdr && \
5985 custom_header_find(compose->account->customhdr_list, header) != NULL)
5987 static void compose_add_headerfield_from_headerlist(Compose *compose,
5989 const gchar *fieldname,
5990 const gchar *seperator)
5992 gchar *str, *fieldname_w_colon;
5993 gboolean add_field = FALSE;
5995 ComposeHeaderEntry *headerentry;
5996 const gchar *headerentryname;
5997 const gchar *trans_fieldname;
6000 if (IS_IN_CUSTOM_HEADER(fieldname))
6003 debug_print("Adding %s-fields\n", fieldname);
6005 fieldstr = g_string_sized_new(64);
6007 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6008 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6010 for (list = compose->header_list; list; list = list->next) {
6011 headerentry = ((ComposeHeaderEntry *)list->data);
6012 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6014 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6015 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6017 if (str[0] != '\0') {
6019 g_string_append(fieldstr, seperator);
6020 g_string_append(fieldstr, str);
6029 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6030 compose_convert_header
6031 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6032 strlen(fieldname) + 2, TRUE);
6033 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6037 g_free(fieldname_w_colon);
6038 g_string_free(fieldstr, TRUE);
6043 static gchar *compose_get_header(Compose *compose)
6045 gchar buf[BUFFSIZE];
6046 const gchar *entry_str;
6050 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6052 gchar *from_name = NULL, *from_address = NULL;
6055 cm_return_val_if_fail(compose->account != NULL, NULL);
6056 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6058 header = g_string_sized_new(64);
6061 get_rfc822_date(buf, sizeof(buf));
6062 g_string_append_printf(header, "Date: %s\n", buf);
6066 if (compose->account->name && *compose->account->name) {
6068 QUOTE_IF_REQUIRED(buf, compose->account->name);
6069 tmp = g_strdup_printf("%s <%s>",
6070 buf, compose->account->address);
6072 tmp = g_strdup_printf("%s",
6073 compose->account->address);
6075 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6076 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6078 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6079 from_address = g_strdup(compose->account->address);
6081 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6082 /* extract name and address */
6083 if (strstr(spec, " <") && strstr(spec, ">")) {
6084 from_address = g_strdup(strrchr(spec, '<')+1);
6085 *(strrchr(from_address, '>')) = '\0';
6086 from_name = g_strdup(spec);
6087 *(strrchr(from_name, '<')) = '\0';
6090 from_address = g_strdup(spec);
6097 if (from_name && *from_name) {
6098 compose_convert_header
6099 (compose, buf, sizeof(buf), from_name,
6100 strlen("From: "), TRUE);
6101 QUOTE_IF_REQUIRED(name, buf);
6103 g_string_append_printf(header, "From: %s <%s>\n",
6104 name, from_address);
6106 g_string_append_printf(header, "From: %s\n", from_address);
6109 g_free(from_address);
6112 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6115 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6118 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6122 * If this account is a NNTP account remove Bcc header from
6123 * message body since it otherwise will be publicly shown
6125 if (compose->account->protocol != A_NNTP)
6126 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6129 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6131 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6134 compose_convert_header(compose, buf, sizeof(buf), str,
6135 strlen("Subject: "), FALSE);
6136 g_string_append_printf(header, "Subject: %s\n", buf);
6142 if (compose->account->set_domain && compose->account->domain) {
6143 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6144 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6145 g_snprintf(buf, sizeof(buf), "%s",
6146 strchr(compose->account->address, '@') ?
6147 strchr(compose->account->address, '@')+1 :
6148 compose->account->address);
6150 g_snprintf(buf, sizeof(buf), "%s", "");
6153 if (compose->account->gen_msgid) {
6155 if (compose->account->msgid_with_addr) {
6156 addr = compose->account->address;
6158 generate_msgid(buf, sizeof(buf), addr);
6159 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6160 compose->msgid = g_strdup(buf);
6162 compose->msgid = NULL;
6165 if (compose->remove_references == FALSE) {
6167 if (compose->inreplyto && compose->to_list)
6168 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6171 if (compose->references)
6172 g_string_append_printf(header, "References: %s\n", compose->references);
6176 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6179 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6182 if (compose->account->organization &&
6183 strlen(compose->account->organization) &&
6184 !IS_IN_CUSTOM_HEADER("Organization")) {
6185 compose_convert_header(compose, buf, sizeof(buf),
6186 compose->account->organization,
6187 strlen("Organization: "), FALSE);
6188 g_string_append_printf(header, "Organization: %s\n", buf);
6191 /* Program version and system info */
6192 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6193 !compose->newsgroup_list) {
6194 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6196 gtk_major_version, gtk_minor_version, gtk_micro_version,
6199 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6200 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6202 gtk_major_version, gtk_minor_version, gtk_micro_version,
6206 /* custom headers */
6207 if (compose->account->add_customhdr) {
6210 for (cur = compose->account->customhdr_list; cur != NULL;
6212 CustomHeader *chdr = (CustomHeader *)cur->data;
6214 if (custom_header_is_allowed(chdr->name)) {
6215 compose_convert_header
6216 (compose, buf, sizeof(buf),
6217 chdr->value ? chdr->value : "",
6218 strlen(chdr->name) + 2, FALSE);
6219 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6224 /* Automatic Faces and X-Faces */
6225 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6226 g_string_append_printf(header, "X-Face: %s\n", buf);
6228 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6229 g_string_append_printf(header, "X-Face: %s\n", buf);
6231 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6232 g_string_append_printf(header, "Face: %s\n", buf);
6234 else if (get_default_face (buf, sizeof(buf)) == 0) {
6235 g_string_append_printf(header, "Face: %s\n", buf);
6239 switch (compose->priority) {
6240 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6241 "X-Priority: 1 (Highest)\n");
6243 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6244 "X-Priority: 2 (High)\n");
6246 case PRIORITY_NORMAL: break;
6247 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6248 "X-Priority: 4 (Low)\n");
6250 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6251 "X-Priority: 5 (Lowest)\n");
6253 default: debug_print("compose: priority unknown : %d\n",
6257 /* Request Return Receipt */
6258 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6259 if (compose->return_receipt) {
6260 if (compose->account->name
6261 && *compose->account->name) {
6262 compose_convert_header(compose, buf, sizeof(buf),
6263 compose->account->name,
6264 strlen("Disposition-Notification-To: "),
6266 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6268 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6272 /* get special headers */
6273 for (list = compose->header_list; list; list = list->next) {
6274 ComposeHeaderEntry *headerentry;
6277 gchar *headername_wcolon;
6278 const gchar *headername_trans;
6281 gboolean standard_header = FALSE;
6283 headerentry = ((ComposeHeaderEntry *)list->data);
6285 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6287 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6292 if (!strstr(tmp, ":")) {
6293 headername_wcolon = g_strconcat(tmp, ":", NULL);
6294 headername = g_strdup(tmp);
6296 headername_wcolon = g_strdup(tmp);
6297 headername = g_strdup(strtok(tmp, ":"));
6301 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6302 Xstrdup_a(headervalue, entry_str, return NULL);
6303 subst_char(headervalue, '\r', ' ');
6304 subst_char(headervalue, '\n', ' ');
6305 string = std_headers;
6306 while (*string != NULL) {
6307 headername_trans = prefs_common_translated_header_name(*string);
6308 if (!strcmp(headername_trans, headername_wcolon))
6309 standard_header = TRUE;
6312 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6313 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6316 g_free(headername_wcolon);
6320 g_string_free(header, FALSE);
6325 #undef IS_IN_CUSTOM_HEADER
6327 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6328 gint header_len, gboolean addr_field)
6330 gchar *tmpstr = NULL;
6331 const gchar *out_codeset = NULL;
6333 cm_return_if_fail(src != NULL);
6334 cm_return_if_fail(dest != NULL);
6336 if (len < 1) return;
6338 tmpstr = g_strdup(src);
6340 subst_char(tmpstr, '\n', ' ');
6341 subst_char(tmpstr, '\r', ' ');
6344 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6345 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6346 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6351 codeconv_set_strict(TRUE);
6352 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6353 conv_get_charset_str(compose->out_encoding));
6354 codeconv_set_strict(FALSE);
6356 if (!dest || *dest == '\0') {
6357 gchar *test_conv_global_out = NULL;
6358 gchar *test_conv_reply = NULL;
6360 /* automatic mode. be automatic. */
6361 codeconv_set_strict(TRUE);
6363 out_codeset = conv_get_outgoing_charset_str();
6365 debug_print("trying to convert to %s\n", out_codeset);
6366 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6369 if (!test_conv_global_out && compose->orig_charset
6370 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6371 out_codeset = compose->orig_charset;
6372 debug_print("failure; trying to convert to %s\n", out_codeset);
6373 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6376 if (!test_conv_global_out && !test_conv_reply) {
6378 out_codeset = CS_INTERNAL;
6379 debug_print("finally using %s\n", out_codeset);
6381 g_free(test_conv_global_out);
6382 g_free(test_conv_reply);
6383 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6385 codeconv_set_strict(FALSE);
6390 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6394 cm_return_if_fail(user_data != NULL);
6396 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6397 g_strstrip(address);
6398 if (*address != '\0') {
6399 gchar *name = procheader_get_fromname(address);
6400 extract_address(address);
6401 addressbook_add_contact(name, address, NULL, NULL);
6406 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6408 GtkWidget *menuitem;
6411 cm_return_if_fail(menu != NULL);
6412 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6414 menuitem = gtk_separator_menu_item_new();
6415 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6416 gtk_widget_show(menuitem);
6418 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6419 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6421 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6422 g_strstrip(address);
6423 if (*address == '\0') {
6424 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6427 g_signal_connect(G_OBJECT(menuitem), "activate",
6428 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6429 gtk_widget_show(menuitem);
6432 static void compose_create_header_entry(Compose *compose)
6434 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6441 const gchar *header = NULL;
6442 ComposeHeaderEntry *headerentry;
6443 gboolean standard_header = FALSE;
6444 GtkListStore *model;
6446 #if !(GTK_CHECK_VERSION(2,12,0))
6447 GtkTooltips *tips = compose->tooltips;
6450 headerentry = g_new0(ComposeHeaderEntry, 1);
6453 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6454 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6455 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6457 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6459 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6461 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6462 COMPOSE_NEWSGROUPS);
6463 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6465 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6466 COMPOSE_FOLLOWUPTO);
6468 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6469 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6470 G_CALLBACK(compose_grab_focus_cb), compose);
6471 gtk_widget_show(combo);
6472 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6473 compose->header_nextrow, compose->header_nextrow+1,
6474 GTK_SHRINK, GTK_FILL, 0, 0);
6475 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6476 const gchar *last_header_entry = gtk_entry_get_text(
6477 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6479 while (*string != NULL) {
6480 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6481 standard_header = TRUE;
6484 if (standard_header)
6485 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6487 if (!compose->header_last || !standard_header) {
6488 switch(compose->account->protocol) {
6490 header = prefs_common_translated_header_name("Newsgroups:");
6493 header = prefs_common_translated_header_name("To:");
6498 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6500 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6501 G_CALLBACK(compose_grab_focus_cb), compose);
6503 /* Entry field with cleanup button */
6504 button = gtk_button_new();
6505 gtk_button_set_image(GTK_BUTTON(button),
6506 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6507 gtk_widget_show(button);
6508 CLAWS_SET_TIP(button,
6509 _("Delete entry contents"));
6510 entry = gtk_entry_new();
6511 gtk_widget_show(entry);
6512 CLAWS_SET_TIP(entry,
6513 _("Use <tab> to autocomplete from addressbook"));
6514 hbox = gtk_hbox_new (FALSE, 0);
6515 gtk_widget_show(hbox);
6516 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6517 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6518 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6519 compose->header_nextrow, compose->header_nextrow+1,
6520 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6522 g_signal_connect(G_OBJECT(entry), "key-press-event",
6523 G_CALLBACK(compose_headerentry_key_press_event_cb),
6525 g_signal_connect(G_OBJECT(entry), "changed",
6526 G_CALLBACK(compose_headerentry_changed_cb),
6528 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6529 G_CALLBACK(compose_grab_focus_cb), compose);
6531 g_signal_connect(G_OBJECT(button), "clicked",
6532 G_CALLBACK(compose_headerentry_button_clicked_cb),
6536 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6537 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6538 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6539 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6540 G_CALLBACK(compose_header_drag_received_cb),
6542 g_signal_connect(G_OBJECT(entry), "drag-drop",
6543 G_CALLBACK(compose_drag_drop),
6545 g_signal_connect(G_OBJECT(entry), "populate-popup",
6546 G_CALLBACK(compose_entry_popup_extend),
6549 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6551 headerentry->compose = compose;
6552 headerentry->combo = combo;
6553 headerentry->entry = entry;
6554 headerentry->button = button;
6555 headerentry->hbox = hbox;
6556 headerentry->headernum = compose->header_nextrow;
6557 headerentry->type = PREF_NONE;
6559 compose->header_nextrow++;
6560 compose->header_last = headerentry;
6561 compose->header_list =
6562 g_slist_append(compose->header_list,
6566 static void compose_add_header_entry(Compose *compose, const gchar *header,
6567 gchar *text, ComposePrefType pref_type)
6569 ComposeHeaderEntry *last_header = compose->header_last;
6570 gchar *tmp = g_strdup(text), *email;
6571 gboolean replyto_hdr;
6573 replyto_hdr = (!strcasecmp(header,
6574 prefs_common_translated_header_name("Reply-To:")) ||
6576 prefs_common_translated_header_name("Followup-To:")) ||
6578 prefs_common_translated_header_name("In-Reply-To:")));
6580 extract_address(tmp);
6581 email = g_utf8_strdown(tmp, -1);
6583 if (replyto_hdr == FALSE &&
6584 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6586 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6587 header, text, (gint) pref_type);
6593 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6594 gtk_entry_set_text(GTK_ENTRY(
6595 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6597 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6598 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6599 last_header->type = pref_type;
6601 if (replyto_hdr == FALSE)
6602 g_hash_table_insert(compose->email_hashtable, email,
6603 GUINT_TO_POINTER(1));
6610 static void compose_destroy_headerentry(Compose *compose,
6611 ComposeHeaderEntry *headerentry)
6613 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6616 extract_address(text);
6617 email = g_utf8_strdown(text, -1);
6618 g_hash_table_remove(compose->email_hashtable, email);
6622 gtk_widget_destroy(headerentry->combo);
6623 gtk_widget_destroy(headerentry->entry);
6624 gtk_widget_destroy(headerentry->button);
6625 gtk_widget_destroy(headerentry->hbox);
6626 g_free(headerentry);
6629 static void compose_remove_header_entries(Compose *compose)
6632 for (list = compose->header_list; list; list = list->next)
6633 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6635 compose->header_last = NULL;
6636 g_slist_free(compose->header_list);
6637 compose->header_list = NULL;
6638 compose->header_nextrow = 1;
6639 compose_create_header_entry(compose);
6642 static GtkWidget *compose_create_header(Compose *compose)
6644 GtkWidget *from_optmenu_hbox;
6645 GtkWidget *header_scrolledwin;
6646 GtkWidget *header_table;
6650 /* header labels and entries */
6651 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6652 gtk_widget_show(header_scrolledwin);
6653 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6655 header_table = gtk_table_new(2, 2, FALSE);
6656 gtk_widget_show(header_table);
6657 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6658 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6659 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6662 /* option menu for selecting accounts */
6663 from_optmenu_hbox = compose_account_option_menu_create(compose);
6664 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6665 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6668 compose->header_table = header_table;
6669 compose->header_list = NULL;
6670 compose->header_nextrow = count;
6672 compose_create_header_entry(compose);
6674 compose->table = NULL;
6676 return header_scrolledwin ;
6679 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6681 Compose *compose = (Compose *)data;
6682 GdkEventButton event;
6685 event.time = gtk_get_current_event_time();
6687 return attach_button_pressed(compose->attach_clist, &event, compose);
6690 static GtkWidget *compose_create_attach(Compose *compose)
6692 GtkWidget *attach_scrwin;
6693 GtkWidget *attach_clist;
6695 GtkListStore *store;
6696 GtkCellRenderer *renderer;
6697 GtkTreeViewColumn *column;
6698 GtkTreeSelection *selection;
6700 /* attachment list */
6701 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6702 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6703 GTK_POLICY_AUTOMATIC,
6704 GTK_POLICY_AUTOMATIC);
6705 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6707 store = gtk_list_store_new(N_ATTACH_COLS,
6712 G_TYPE_AUTO_POINTER,
6714 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6715 (GTK_TREE_MODEL(store)));
6716 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6717 g_object_unref(store);
6719 renderer = gtk_cell_renderer_text_new();
6720 column = gtk_tree_view_column_new_with_attributes
6721 (_("Mime type"), renderer, "text",
6722 COL_MIMETYPE, NULL);
6723 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6725 renderer = gtk_cell_renderer_text_new();
6726 column = gtk_tree_view_column_new_with_attributes
6727 (_("Size"), renderer, "text",
6729 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6731 renderer = gtk_cell_renderer_text_new();
6732 column = gtk_tree_view_column_new_with_attributes
6733 (_("Name"), renderer, "text",
6735 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6737 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6738 prefs_common.use_stripes_everywhere);
6739 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6740 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6742 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6743 G_CALLBACK(attach_selected), compose);
6744 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6745 G_CALLBACK(attach_button_pressed), compose);
6747 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6748 G_CALLBACK(popup_attach_button_pressed), compose);
6750 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6751 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6752 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6753 G_CALLBACK(popup_attach_button_pressed), compose);
6755 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6756 G_CALLBACK(attach_key_pressed), compose);
6759 gtk_drag_dest_set(attach_clist,
6760 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6761 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6762 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6763 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6764 G_CALLBACK(compose_attach_drag_received_cb),
6766 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6767 G_CALLBACK(compose_drag_drop),
6770 compose->attach_scrwin = attach_scrwin;
6771 compose->attach_clist = attach_clist;
6773 return attach_scrwin;
6776 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6777 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6779 static GtkWidget *compose_create_others(Compose *compose)
6782 GtkWidget *savemsg_checkbtn;
6783 GtkWidget *savemsg_combo;
6784 GtkWidget *savemsg_select;
6787 gchar *folderidentifier;
6789 /* Table for settings */
6790 table = gtk_table_new(3, 1, FALSE);
6791 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6792 gtk_widget_show(table);
6793 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6796 /* Save Message to folder */
6797 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6798 gtk_widget_show(savemsg_checkbtn);
6799 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6800 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6801 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6803 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6804 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6806 savemsg_combo = gtk_combo_box_entry_new_text();
6807 compose->savemsg_checkbtn = savemsg_checkbtn;
6808 compose->savemsg_combo = savemsg_combo;
6809 gtk_widget_show(savemsg_combo);
6811 if (prefs_common.compose_save_to_history)
6812 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6813 prefs_common.compose_save_to_history);
6815 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6816 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6817 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6818 G_CALLBACK(compose_grab_focus_cb), compose);
6819 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6820 folderidentifier = folder_item_get_identifier(account_get_special_folder
6821 (compose->account, F_OUTBOX));
6822 compose_set_save_to(compose, folderidentifier);
6823 g_free(folderidentifier);
6826 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6827 gtk_widget_show(savemsg_select);
6828 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6829 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6830 G_CALLBACK(compose_savemsg_select_cb),
6838 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6840 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6841 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6844 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6849 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6852 path = folder_item_get_identifier(dest);
6854 compose_set_save_to(compose, path);
6858 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6859 GdkAtom clip, GtkTextIter *insert_place);
6862 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6866 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6868 if (event->button == 3) {
6870 GtkTextIter sel_start, sel_end;
6871 gboolean stuff_selected;
6873 /* move the cursor to allow GtkAspell to check the word
6874 * under the mouse */
6875 if (event->x && event->y) {
6876 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6877 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6879 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6882 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6883 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6886 stuff_selected = gtk_text_buffer_get_selection_bounds(
6888 &sel_start, &sel_end);
6890 gtk_text_buffer_place_cursor (buffer, &iter);
6891 /* reselect stuff */
6893 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6894 gtk_text_buffer_select_range(buffer,
6895 &sel_start, &sel_end);
6897 return FALSE; /* pass the event so that the right-click goes through */
6900 if (event->button == 2) {
6905 /* get the middle-click position to paste at the correct place */
6906 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6907 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6909 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6912 entry_paste_clipboard(compose, text,
6913 prefs_common.linewrap_pastes,
6914 GDK_SELECTION_PRIMARY, &iter);
6922 static void compose_spell_menu_changed(void *data)
6924 Compose *compose = (Compose *)data;
6926 GtkWidget *menuitem;
6927 GtkWidget *parent_item;
6928 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6931 if (compose->gtkaspell == NULL)
6934 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6935 "/Menu/Spelling/Options");
6937 /* setting the submenu removes /Spelling/Options from the factory
6938 * so we need to save it */
6940 if (parent_item == NULL) {
6941 parent_item = compose->aspell_options_menu;
6942 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6944 compose->aspell_options_menu = parent_item;
6946 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6948 spell_menu = g_slist_reverse(spell_menu);
6949 for (items = spell_menu;
6950 items; items = items->next) {
6951 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6952 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6953 gtk_widget_show(GTK_WIDGET(menuitem));
6955 g_slist_free(spell_menu);
6957 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6958 gtk_widget_show(parent_item);
6961 static void compose_dict_changed(void *data)
6963 Compose *compose = (Compose *) data;
6965 if(compose->gtkaspell &&
6966 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6969 gtkaspell_highlight_all(compose->gtkaspell);
6970 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6974 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6976 Compose *compose = (Compose *)data;
6977 GdkEventButton event;
6980 event.time = gtk_get_current_event_time();
6984 return text_clicked(compose->text, &event, compose);
6987 static gboolean compose_force_window_origin = TRUE;
6988 static Compose *compose_create(PrefsAccount *account,
6997 GtkWidget *handlebox;
6999 GtkWidget *notebook;
7001 GtkWidget *attach_hbox;
7002 GtkWidget *attach_lab1;
7003 GtkWidget *attach_lab2;
7008 GtkWidget *subject_hbox;
7009 GtkWidget *subject_frame;
7010 GtkWidget *subject_entry;
7014 GtkWidget *edit_vbox;
7015 GtkWidget *ruler_hbox;
7017 GtkWidget *scrolledwin;
7019 GtkTextBuffer *buffer;
7020 GtkClipboard *clipboard;
7023 UndoMain *undostruct;
7025 gchar *titles[N_ATTACH_COLS];
7026 GtkWidget *popupmenu;
7027 GtkWidget *tmpl_menu;
7028 GtkActionGroup *action_group = NULL;
7031 GtkAspell * gtkaspell = NULL;
7034 static GdkGeometry geometry;
7036 cm_return_val_if_fail(account != NULL, NULL);
7038 debug_print("Creating compose window...\n");
7039 compose = g_new0(Compose, 1);
7041 titles[COL_MIMETYPE] = _("MIME type");
7042 titles[COL_SIZE] = _("Size");
7043 titles[COL_NAME] = _("Name");
7045 compose->batch = batch;
7046 compose->account = account;
7047 compose->folder = folder;
7049 compose->mutex = g_mutex_new();
7050 compose->set_cursor_pos = -1;
7052 #if !(GTK_CHECK_VERSION(2,12,0))
7053 compose->tooltips = tips;
7056 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7058 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7059 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7061 if (!geometry.max_width) {
7062 geometry.max_width = gdk_screen_width();
7063 geometry.max_height = gdk_screen_height();
7066 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7067 &geometry, GDK_HINT_MAX_SIZE);
7068 if (!geometry.min_width) {
7069 geometry.min_width = 600;
7070 geometry.min_height = 440;
7072 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7073 &geometry, GDK_HINT_MIN_SIZE);
7075 #ifndef GENERIC_UMPC
7076 if (compose_force_window_origin)
7077 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7078 prefs_common.compose_y);
7080 g_signal_connect(G_OBJECT(window), "delete_event",
7081 G_CALLBACK(compose_delete_cb), compose);
7082 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7083 gtk_widget_realize(window);
7085 gtkut_widget_set_composer_icon(window);
7087 vbox = gtk_vbox_new(FALSE, 0);
7088 gtk_container_add(GTK_CONTAINER(window), vbox);
7090 compose->ui_manager = gtk_ui_manager_new();
7091 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7092 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7093 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7094 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7095 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7096 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7097 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7098 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7099 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7100 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7103 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7105 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7111 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7152 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7159 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7191 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7217 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7219 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7220 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)
7221 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)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7227 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)
7228 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)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7233 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)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7237 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)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7240 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7243 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)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7245 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7246 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7248 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7249 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)
7250 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)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7255 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7257 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7258 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7259 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7263 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)
7265 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7267 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7271 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7272 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7273 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7275 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7276 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7279 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7281 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7282 gtk_widget_show_all(menubar);
7284 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7286 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7288 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7291 if (prefs_common.toolbar_detachable) {
7292 handlebox = gtk_handle_box_new();
7294 handlebox = gtk_hbox_new(FALSE, 0);
7296 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7298 gtk_widget_realize(handlebox);
7300 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7303 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7307 vbox2 = gtk_vbox_new(FALSE, 2);
7308 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7309 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7312 notebook = gtk_notebook_new();
7313 gtk_widget_set_size_request(notebook, -1, 130);
7314 gtk_widget_show(notebook);
7316 /* header labels and entries */
7317 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7318 compose_create_header(compose),
7319 gtk_label_new_with_mnemonic(_("Hea_der")));
7320 /* attachment list */
7321 attach_hbox = gtk_hbox_new(FALSE, 0);
7322 gtk_widget_show(attach_hbox);
7324 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7325 gtk_widget_show(attach_lab1);
7326 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7328 attach_lab2 = gtk_label_new("");
7329 gtk_widget_show(attach_lab2);
7330 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7332 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7333 compose_create_attach(compose),
7336 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7337 compose_create_others(compose),
7338 gtk_label_new_with_mnemonic(_("Othe_rs")));
7341 subject_hbox = gtk_hbox_new(FALSE, 0);
7342 gtk_widget_show(subject_hbox);
7344 subject_frame = gtk_frame_new(NULL);
7345 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7346 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7347 gtk_widget_show(subject_frame);
7349 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7350 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7351 gtk_widget_show(subject);
7353 label = gtk_label_new(_("Subject:"));
7354 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7355 gtk_widget_show(label);
7358 subject_entry = claws_spell_entry_new();
7360 subject_entry = gtk_entry_new();
7362 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7363 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7364 G_CALLBACK(compose_grab_focus_cb), compose);
7365 gtk_widget_show(subject_entry);
7366 compose->subject_entry = subject_entry;
7367 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7369 edit_vbox = gtk_vbox_new(FALSE, 0);
7371 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7374 ruler_hbox = gtk_hbox_new(FALSE, 0);
7375 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7377 ruler = gtk_shruler_new();
7378 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7379 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7383 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7384 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7385 GTK_POLICY_AUTOMATIC,
7386 GTK_POLICY_AUTOMATIC);
7387 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7389 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7390 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7392 text = gtk_text_view_new();
7393 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7394 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7395 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7396 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7397 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7399 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7401 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7402 G_CALLBACK(compose_edit_size_alloc),
7404 g_signal_connect(G_OBJECT(buffer), "changed",
7405 G_CALLBACK(compose_changed_cb), compose);
7406 g_signal_connect(G_OBJECT(text), "grab_focus",
7407 G_CALLBACK(compose_grab_focus_cb), compose);
7408 g_signal_connect(G_OBJECT(buffer), "insert_text",
7409 G_CALLBACK(text_inserted), compose);
7410 g_signal_connect(G_OBJECT(text), "button_press_event",
7411 G_CALLBACK(text_clicked), compose);
7413 g_signal_connect(G_OBJECT(text), "popup-menu",
7414 G_CALLBACK(compose_popup_menu), compose);
7416 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7417 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7418 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7419 G_CALLBACK(compose_popup_menu), compose);
7421 g_signal_connect(G_OBJECT(subject_entry), "changed",
7422 G_CALLBACK(compose_changed_cb), compose);
7425 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7426 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7427 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7428 g_signal_connect(G_OBJECT(text), "drag_data_received",
7429 G_CALLBACK(compose_insert_drag_received_cb),
7431 g_signal_connect(G_OBJECT(text), "drag-drop",
7432 G_CALLBACK(compose_drag_drop),
7434 gtk_widget_show_all(vbox);
7436 /* pane between attach clist and text */
7437 paned = gtk_vpaned_new();
7438 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7440 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7441 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7443 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7445 gtk_paned_add1(GTK_PANED(paned), notebook);
7446 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7447 gtk_widget_show_all(paned);
7450 if (prefs_common.textfont) {
7451 PangoFontDescription *font_desc;
7453 font_desc = pango_font_description_from_string
7454 (prefs_common.textfont);
7456 gtk_widget_modify_font(text, font_desc);
7457 pango_font_description_free(font_desc);
7461 gtk_action_group_add_actions(action_group, compose_popup_entries,
7462 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7467 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7470 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7474 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7476 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7478 undostruct = undo_init(text);
7479 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7482 address_completion_start(window);
7484 compose->window = window;
7485 compose->vbox = vbox;
7486 compose->menubar = menubar;
7487 compose->handlebox = handlebox;
7489 compose->vbox2 = vbox2;
7491 compose->paned = paned;
7493 compose->attach_label = attach_lab2;
7495 compose->notebook = notebook;
7496 compose->edit_vbox = edit_vbox;
7497 compose->ruler_hbox = ruler_hbox;
7498 compose->ruler = ruler;
7499 compose->scrolledwin = scrolledwin;
7500 compose->text = text;
7502 compose->focused_editable = NULL;
7504 compose->popupmenu = popupmenu;
7506 compose->tmpl_menu = tmpl_menu;
7508 compose->mode = mode;
7509 compose->rmode = mode;
7511 compose->targetinfo = NULL;
7512 compose->replyinfo = NULL;
7513 compose->fwdinfo = NULL;
7515 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7516 g_str_equal, (GDestroyNotify) g_free, NULL);
7518 compose->replyto = NULL;
7520 compose->bcc = NULL;
7521 compose->followup_to = NULL;
7523 compose->ml_post = NULL;
7525 compose->inreplyto = NULL;
7526 compose->references = NULL;
7527 compose->msgid = NULL;
7528 compose->boundary = NULL;
7530 compose->autowrap = prefs_common.autowrap;
7531 compose->autoindent = prefs_common.auto_indent;
7532 compose->use_signing = FALSE;
7533 compose->use_encryption = FALSE;
7534 compose->privacy_system = NULL;
7536 compose->modified = FALSE;
7538 compose->return_receipt = FALSE;
7540 compose->to_list = NULL;
7541 compose->newsgroup_list = NULL;
7543 compose->undostruct = undostruct;
7545 compose->sig_str = NULL;
7547 compose->exteditor_file = NULL;
7548 compose->exteditor_pid = -1;
7549 compose->exteditor_tag = -1;
7550 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7554 if (mode != COMPOSE_REDIRECT) {
7555 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7556 strcmp(prefs_common.dictionary, "")) {
7557 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7558 prefs_common.alt_dictionary,
7559 conv_get_locale_charset_str(),
7560 prefs_common.misspelled_col,
7561 prefs_common.check_while_typing,
7562 prefs_common.recheck_when_changing_dict,
7563 prefs_common.use_alternate,
7564 prefs_common.use_both_dicts,
7565 GTK_TEXT_VIEW(text),
7566 GTK_WINDOW(compose->window),
7567 compose_dict_changed,
7568 compose_spell_menu_changed,
7571 alertpanel_error(_("Spell checker could not "
7573 gtkaspell_checkers_strerror());
7574 gtkaspell_checkers_reset_error();
7576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7580 compose->gtkaspell = gtkaspell;
7581 compose_spell_menu_changed(compose);
7582 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7585 compose_select_account(compose, account, TRUE);
7587 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7588 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7590 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7591 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7593 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7594 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7596 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7597 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7599 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7600 if (account->protocol != A_NNTP)
7601 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7602 prefs_common_translated_header_name("To:"));
7604 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7605 prefs_common_translated_header_name("Newsgroups:"));
7607 addressbook_set_target_compose(compose);
7609 if (mode != COMPOSE_REDIRECT)
7610 compose_set_template_menu(compose);
7612 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7615 compose_list = g_list_append(compose_list, compose);
7617 if (!prefs_common.show_ruler)
7618 gtk_widget_hide(ruler_hbox);
7620 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7623 compose->priority = PRIORITY_NORMAL;
7624 compose_update_priority_menu_item(compose);
7626 compose_set_out_encoding(compose);
7629 compose_update_actions_menu(compose);
7631 /* Privacy Systems menu */
7632 compose_update_privacy_systems_menu(compose);
7634 activate_privacy_system(compose, account, TRUE);
7635 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7637 gtk_widget_realize(window);
7639 gtk_widget_show(window);
7641 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7642 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7649 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7654 GtkWidget *optmenubox;
7657 GtkWidget *from_name = NULL;
7658 #if !(GTK_CHECK_VERSION(2,12,0))
7659 GtkTooltips *tips = compose->tooltips;
7662 gint num = 0, def_menu = 0;
7664 accounts = account_get_list();
7665 cm_return_val_if_fail(accounts != NULL, NULL);
7667 optmenubox = gtk_event_box_new();
7668 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7669 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7671 hbox = gtk_hbox_new(FALSE, 6);
7672 from_name = gtk_entry_new();
7674 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7675 G_CALLBACK(compose_grab_focus_cb), compose);
7677 for (; accounts != NULL; accounts = accounts->next, num++) {
7678 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7679 gchar *name, *from = NULL;
7681 if (ac == compose->account) def_menu = num;
7683 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7686 if (ac == compose->account) {
7687 if (ac->name && *ac->name) {
7689 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7690 from = g_strdup_printf("%s <%s>",
7692 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7694 from = g_strdup_printf("%s",
7696 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7699 COMBOBOX_ADD(menu, name, ac->account_id);
7704 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7706 g_signal_connect(G_OBJECT(optmenu), "changed",
7707 G_CALLBACK(account_activated),
7709 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7710 G_CALLBACK(compose_entry_popup_extend),
7713 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7714 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7716 CLAWS_SET_TIP(optmenubox,
7717 _("Account to use for this email"));
7718 CLAWS_SET_TIP(from_name,
7719 _("Sender address to be used"));
7721 compose->from_name = from_name;
7726 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7728 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7729 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7730 Compose *compose = (Compose *) data;
7732 compose->priority = value;
7736 static void compose_reply_change_mode(Compose *compose,
7739 gboolean was_modified = compose->modified;
7741 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7743 cm_return_if_fail(compose->replyinfo != NULL);
7745 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7747 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7749 if (action == COMPOSE_REPLY_TO_ALL)
7751 if (action == COMPOSE_REPLY_TO_SENDER)
7753 if (action == COMPOSE_REPLY_TO_LIST)
7756 compose_remove_header_entries(compose);
7757 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7758 if (compose->account->set_autocc && compose->account->auto_cc)
7759 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7761 if (compose->account->set_autobcc && compose->account->auto_bcc)
7762 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7764 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7765 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7766 compose_show_first_last_header(compose, TRUE);
7767 compose->modified = was_modified;
7768 compose_set_title(compose);
7771 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7773 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7774 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7775 Compose *compose = (Compose *) data;
7778 compose_reply_change_mode(compose, value);
7781 static void compose_update_priority_menu_item(Compose * compose)
7783 GtkWidget *menuitem = NULL;
7784 switch (compose->priority) {
7785 case PRIORITY_HIGHEST:
7786 menuitem = gtk_ui_manager_get_widget
7787 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7790 menuitem = gtk_ui_manager_get_widget
7791 (compose->ui_manager, "/Menu/Options/Priority/High");
7793 case PRIORITY_NORMAL:
7794 menuitem = gtk_ui_manager_get_widget
7795 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7798 menuitem = gtk_ui_manager_get_widget
7799 (compose->ui_manager, "/Menu/Options/Priority/Low");
7801 case PRIORITY_LOWEST:
7802 menuitem = gtk_ui_manager_get_widget
7803 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7806 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7809 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7811 Compose *compose = (Compose *) data;
7813 gboolean can_sign = FALSE, can_encrypt = FALSE;
7815 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7817 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7820 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7821 g_free(compose->privacy_system);
7822 compose->privacy_system = NULL;
7823 if (systemid != NULL) {
7824 compose->privacy_system = g_strdup(systemid);
7826 can_sign = privacy_system_can_sign(systemid);
7827 can_encrypt = privacy_system_can_encrypt(systemid);
7830 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7832 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7833 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7836 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7838 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7839 GtkWidget *menuitem = NULL;
7841 gboolean can_sign = FALSE, can_encrypt = FALSE;
7842 gboolean found = FALSE;
7844 if (compose->privacy_system != NULL) {
7846 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7847 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7848 cm_return_if_fail(menuitem != NULL);
7850 amenu = GTK_MENU_SHELL(menuitem)->children;
7852 while (amenu != NULL) {
7853 GList *alist = amenu->next;
7855 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7856 if (systemid != NULL) {
7857 if (strcmp(systemid, compose->privacy_system) == 0 &&
7858 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7859 menuitem = GTK_WIDGET(amenu->data);
7861 can_sign = privacy_system_can_sign(systemid);
7862 can_encrypt = privacy_system_can_encrypt(systemid);
7866 } else if (strlen(compose->privacy_system) == 0 &&
7867 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7868 menuitem = GTK_WIDGET(amenu->data);
7871 can_encrypt = FALSE;
7878 if (menuitem != NULL)
7879 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7881 if (warn && !found && strlen(compose->privacy_system)) {
7882 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7883 "will not be able to sign or encrypt this message."),
7884 compose->privacy_system);
7888 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7889 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7892 static void compose_set_out_encoding(Compose *compose)
7894 CharSet out_encoding;
7895 const gchar *branch = NULL;
7896 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7898 switch(out_encoding) {
7899 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7900 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7901 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7902 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7903 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7904 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7905 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7906 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7907 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7908 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7909 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7910 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7911 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7912 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7913 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7914 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7915 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7916 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7917 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7918 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7919 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7920 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7921 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7922 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
7923 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7924 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7925 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7926 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7927 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7928 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7929 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7930 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7931 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7933 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7936 static void compose_set_template_menu(Compose *compose)
7938 GSList *tmpl_list, *cur;
7942 tmpl_list = template_get_config();
7944 menu = gtk_menu_new();
7946 gtk_menu_set_accel_group (GTK_MENU (menu),
7947 gtk_ui_manager_get_accel_group(compose->ui_manager));
7948 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7949 Template *tmpl = (Template *)cur->data;
7950 gchar *accel_path = NULL;
7951 item = gtk_menu_item_new_with_label(tmpl->name);
7952 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7953 g_signal_connect(G_OBJECT(item), "activate",
7954 G_CALLBACK(compose_template_activate_cb),
7956 g_object_set_data(G_OBJECT(item), "template", tmpl);
7957 gtk_widget_show(item);
7958 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7959 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7963 gtk_widget_show(menu);
7964 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7967 void compose_update_actions_menu(Compose *compose)
7969 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7972 static void compose_update_privacy_systems_menu(Compose *compose)
7974 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7975 GSList *systems, *cur;
7977 GtkWidget *system_none;
7979 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7980 GtkWidget *privacy_menu = gtk_menu_new();
7982 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7983 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7985 g_signal_connect(G_OBJECT(system_none), "activate",
7986 G_CALLBACK(compose_set_privacy_system_cb), compose);
7988 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7989 gtk_widget_show(system_none);
7991 systems = privacy_get_system_ids();
7992 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7993 gchar *systemid = cur->data;
7995 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7996 widget = gtk_radio_menu_item_new_with_label(group,
7997 privacy_system_get_name(systemid));
7998 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7999 g_strdup(systemid), g_free);
8000 g_signal_connect(G_OBJECT(widget), "activate",
8001 G_CALLBACK(compose_set_privacy_system_cb), compose);
8003 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8004 gtk_widget_show(widget);
8007 g_slist_free(systems);
8008 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8009 gtk_widget_show_all(privacy_menu);
8010 gtk_widget_show_all(privacy_menuitem);
8013 void compose_reflect_prefs_all(void)
8018 for (cur = compose_list; cur != NULL; cur = cur->next) {
8019 compose = (Compose *)cur->data;
8020 compose_set_template_menu(compose);
8024 void compose_reflect_prefs_pixmap_theme(void)
8029 for (cur = compose_list; cur != NULL; cur = cur->next) {
8030 compose = (Compose *)cur->data;
8031 toolbar_update(TOOLBAR_COMPOSE, compose);
8035 static const gchar *compose_quote_char_from_context(Compose *compose)
8037 const gchar *qmark = NULL;
8039 cm_return_val_if_fail(compose != NULL, NULL);
8041 switch (compose->mode) {
8042 /* use forward-specific quote char */
8043 case COMPOSE_FORWARD:
8044 case COMPOSE_FORWARD_AS_ATTACH:
8045 case COMPOSE_FORWARD_INLINE:
8046 if (compose->folder && compose->folder->prefs &&
8047 compose->folder->prefs->forward_with_format)
8048 qmark = compose->folder->prefs->forward_quotemark;
8049 else if (compose->account->forward_with_format)
8050 qmark = compose->account->forward_quotemark;
8052 qmark = prefs_common.fw_quotemark;
8055 /* use reply-specific quote char in all other modes */
8057 if (compose->folder && compose->folder->prefs &&
8058 compose->folder->prefs->reply_with_format)
8059 qmark = compose->folder->prefs->reply_quotemark;
8060 else if (compose->account->reply_with_format)
8061 qmark = compose->account->reply_quotemark;
8063 qmark = prefs_common.quotemark;
8067 if (qmark == NULL || *qmark == '\0')
8073 static void compose_template_apply(Compose *compose, Template *tmpl,
8077 GtkTextBuffer *buffer;
8081 gchar *parsed_str = NULL;
8082 gint cursor_pos = 0;
8083 const gchar *err_msg = _("The body of the template has an error at line %d.");
8086 /* process the body */
8088 text = GTK_TEXT_VIEW(compose->text);
8089 buffer = gtk_text_view_get_buffer(text);
8092 qmark = compose_quote_char_from_context(compose);
8094 if (compose->replyinfo != NULL) {
8097 gtk_text_buffer_set_text(buffer, "", -1);
8098 mark = gtk_text_buffer_get_insert(buffer);
8099 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8101 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8102 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8104 } else if (compose->fwdinfo != NULL) {
8107 gtk_text_buffer_set_text(buffer, "", -1);
8108 mark = gtk_text_buffer_get_insert(buffer);
8109 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8111 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8112 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8115 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8117 GtkTextIter start, end;
8120 gtk_text_buffer_get_start_iter(buffer, &start);
8121 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8122 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8124 /* clear the buffer now */
8126 gtk_text_buffer_set_text(buffer, "", -1);
8128 parsed_str = compose_quote_fmt(compose, dummyinfo,
8129 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8130 procmsg_msginfo_free( dummyinfo );
8136 gtk_text_buffer_set_text(buffer, "", -1);
8137 mark = gtk_text_buffer_get_insert(buffer);
8138 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8141 if (replace && parsed_str && compose->account->auto_sig)
8142 compose_insert_sig(compose, FALSE);
8144 if (replace && parsed_str) {
8145 gtk_text_buffer_get_start_iter(buffer, &iter);
8146 gtk_text_buffer_place_cursor(buffer, &iter);
8150 cursor_pos = quote_fmt_get_cursor_pos();
8151 compose->set_cursor_pos = cursor_pos;
8152 if (cursor_pos == -1)
8154 gtk_text_buffer_get_start_iter(buffer, &iter);
8155 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8156 gtk_text_buffer_place_cursor(buffer, &iter);
8159 /* process the other fields */
8161 compose_template_apply_fields(compose, tmpl);
8162 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8163 quote_fmt_reset_vartable();
8164 compose_changed_cb(NULL, compose);
8167 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8168 gtkaspell_highlight_all(compose->gtkaspell);
8172 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8174 MsgInfo* dummyinfo = NULL;
8175 MsgInfo *msginfo = NULL;
8178 if (compose->replyinfo != NULL)
8179 msginfo = compose->replyinfo;
8180 else if (compose->fwdinfo != NULL)
8181 msginfo = compose->fwdinfo;
8183 dummyinfo = compose_msginfo_new_from_compose(compose);
8184 msginfo = dummyinfo;
8187 if (tmpl->from && *tmpl->from != '\0') {
8189 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8190 compose->gtkaspell);
8192 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8194 quote_fmt_scan_string(tmpl->from);
8197 buf = quote_fmt_get_buffer();
8199 alertpanel_error(_("Template From format error."));
8201 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8205 if (tmpl->to && *tmpl->to != '\0') {
8207 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8208 compose->gtkaspell);
8210 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8212 quote_fmt_scan_string(tmpl->to);
8215 buf = quote_fmt_get_buffer();
8217 alertpanel_error(_("Template To format error."));
8219 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8223 if (tmpl->cc && *tmpl->cc != '\0') {
8225 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8226 compose->gtkaspell);
8228 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8230 quote_fmt_scan_string(tmpl->cc);
8233 buf = quote_fmt_get_buffer();
8235 alertpanel_error(_("Template Cc format error."));
8237 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8241 if (tmpl->bcc && *tmpl->bcc != '\0') {
8243 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8244 compose->gtkaspell);
8246 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8248 quote_fmt_scan_string(tmpl->bcc);
8251 buf = quote_fmt_get_buffer();
8253 alertpanel_error(_("Template Bcc format error."));
8255 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8259 /* process the subject */
8260 if (tmpl->subject && *tmpl->subject != '\0') {
8262 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8263 compose->gtkaspell);
8265 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8267 quote_fmt_scan_string(tmpl->subject);
8270 buf = quote_fmt_get_buffer();
8272 alertpanel_error(_("Template subject format error."));
8274 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8278 procmsg_msginfo_free( dummyinfo );
8281 static void compose_destroy(Compose *compose)
8283 GtkTextBuffer *buffer;
8284 GtkClipboard *clipboard;
8286 compose_list = g_list_remove(compose_list, compose);
8288 if (compose->updating) {
8289 debug_print("danger, not destroying anything now\n");
8290 compose->deferred_destroy = TRUE;
8293 /* NOTE: address_completion_end() does nothing with the window
8294 * however this may change. */
8295 address_completion_end(compose->window);
8297 slist_free_strings(compose->to_list);
8298 g_slist_free(compose->to_list);
8299 slist_free_strings(compose->newsgroup_list);
8300 g_slist_free(compose->newsgroup_list);
8301 slist_free_strings(compose->header_list);
8302 g_slist_free(compose->header_list);
8304 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8306 g_hash_table_destroy(compose->email_hashtable);
8308 procmsg_msginfo_free(compose->targetinfo);
8309 procmsg_msginfo_free(compose->replyinfo);
8310 procmsg_msginfo_free(compose->fwdinfo);
8312 g_free(compose->replyto);
8313 g_free(compose->cc);
8314 g_free(compose->bcc);
8315 g_free(compose->newsgroups);
8316 g_free(compose->followup_to);
8318 g_free(compose->ml_post);
8320 g_free(compose->inreplyto);
8321 g_free(compose->references);
8322 g_free(compose->msgid);
8323 g_free(compose->boundary);
8325 g_free(compose->redirect_filename);
8326 if (compose->undostruct)
8327 undo_destroy(compose->undostruct);
8329 g_free(compose->sig_str);
8331 g_free(compose->exteditor_file);
8333 g_free(compose->orig_charset);
8335 g_free(compose->privacy_system);
8337 if (addressbook_get_target_compose() == compose)
8338 addressbook_set_target_compose(NULL);
8341 if (compose->gtkaspell) {
8342 gtkaspell_delete(compose->gtkaspell);
8343 compose->gtkaspell = NULL;
8347 if (!compose->batch) {
8348 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8349 prefs_common.compose_height = compose->window->allocation.height;
8352 if (!gtk_widget_get_parent(compose->paned))
8353 gtk_widget_destroy(compose->paned);
8354 gtk_widget_destroy(compose->popupmenu);
8356 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8357 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8358 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8360 gtk_widget_destroy(compose->window);
8361 toolbar_destroy(compose->toolbar);
8362 g_free(compose->toolbar);
8363 g_mutex_free(compose->mutex);
8367 static void compose_attach_info_free(AttachInfo *ainfo)
8369 g_free(ainfo->file);
8370 g_free(ainfo->content_type);
8371 g_free(ainfo->name);
8375 static void compose_attach_update_label(Compose *compose)
8380 GtkTreeModel *model;
8385 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8386 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8387 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8391 while(gtk_tree_model_iter_next(model, &iter))
8394 text = g_strdup_printf("(%d)", i);
8395 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8399 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8401 Compose *compose = (Compose *)data;
8402 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8403 GtkTreeSelection *selection;
8405 GtkTreeModel *model;
8407 selection = gtk_tree_view_get_selection(tree_view);
8408 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8413 for (cur = sel; cur != NULL; cur = cur->next) {
8414 GtkTreePath *path = cur->data;
8415 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8418 gtk_tree_path_free(path);
8421 for (cur = sel; cur != NULL; cur = cur->next) {
8422 GtkTreeRowReference *ref = cur->data;
8423 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8426 if (gtk_tree_model_get_iter(model, &iter, path))
8427 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8429 gtk_tree_path_free(path);
8430 gtk_tree_row_reference_free(ref);
8434 compose_attach_update_label(compose);
8437 static struct _AttachProperty
8440 GtkWidget *mimetype_entry;
8441 GtkWidget *encoding_optmenu;
8442 GtkWidget *path_entry;
8443 GtkWidget *filename_entry;
8445 GtkWidget *cancel_btn;
8448 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8450 gtk_tree_path_free((GtkTreePath *)ptr);
8453 static void compose_attach_property(GtkAction *action, gpointer data)
8455 Compose *compose = (Compose *)data;
8456 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8458 GtkComboBox *optmenu;
8459 GtkTreeSelection *selection;
8461 GtkTreeModel *model;
8464 static gboolean cancelled;
8466 /* only if one selected */
8467 selection = gtk_tree_view_get_selection(tree_view);
8468 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8471 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8475 path = (GtkTreePath *) sel->data;
8476 gtk_tree_model_get_iter(model, &iter, path);
8477 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8480 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8486 if (!attach_prop.window)
8487 compose_attach_property_create(&cancelled);
8488 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8489 gtk_widget_grab_focus(attach_prop.ok_btn);
8490 gtk_widget_show(attach_prop.window);
8491 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8493 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8494 if (ainfo->encoding == ENC_UNKNOWN)
8495 combobox_select_by_data(optmenu, ENC_BASE64);
8497 combobox_select_by_data(optmenu, ainfo->encoding);
8499 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8500 ainfo->content_type ? ainfo->content_type : "");
8501 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8502 ainfo->file ? ainfo->file : "");
8503 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8504 ainfo->name ? ainfo->name : "");
8507 const gchar *entry_text;
8509 gchar *cnttype = NULL;
8516 gtk_widget_hide(attach_prop.window);
8517 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8522 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8523 if (*entry_text != '\0') {
8526 text = g_strstrip(g_strdup(entry_text));
8527 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8528 cnttype = g_strdup(text);
8531 alertpanel_error(_("Invalid MIME type."));
8537 ainfo->encoding = combobox_get_active_data(optmenu);
8539 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8540 if (*entry_text != '\0') {
8541 if (is_file_exist(entry_text) &&
8542 (size = get_file_size(entry_text)) > 0)
8543 file = g_strdup(entry_text);
8546 (_("File doesn't exist or is empty."));
8552 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8553 if (*entry_text != '\0') {
8554 g_free(ainfo->name);
8555 ainfo->name = g_strdup(entry_text);
8559 g_free(ainfo->content_type);
8560 ainfo->content_type = cnttype;
8563 g_free(ainfo->file);
8567 ainfo->size = (goffset)size;
8569 /* update tree store */
8570 text = to_human_readable(ainfo->size);
8571 gtk_tree_model_get_iter(model, &iter, path);
8572 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8573 COL_MIMETYPE, ainfo->content_type,
8575 COL_NAME, ainfo->name,
8581 gtk_tree_path_free(path);
8584 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8586 label = gtk_label_new(str); \
8587 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8588 GTK_FILL, 0, 0, 0); \
8589 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8591 entry = gtk_entry_new(); \
8592 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8593 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8596 static void compose_attach_property_create(gboolean *cancelled)
8602 GtkWidget *mimetype_entry;
8605 GtkListStore *optmenu_menu;
8606 GtkWidget *path_entry;
8607 GtkWidget *filename_entry;
8610 GtkWidget *cancel_btn;
8611 GList *mime_type_list, *strlist;
8614 debug_print("Creating attach_property window...\n");
8616 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8617 gtk_widget_set_size_request(window, 480, -1);
8618 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8619 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8620 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8621 g_signal_connect(G_OBJECT(window), "delete_event",
8622 G_CALLBACK(attach_property_delete_event),
8624 g_signal_connect(G_OBJECT(window), "key_press_event",
8625 G_CALLBACK(attach_property_key_pressed),
8628 vbox = gtk_vbox_new(FALSE, 8);
8629 gtk_container_add(GTK_CONTAINER(window), vbox);
8631 table = gtk_table_new(4, 2, FALSE);
8632 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8633 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8634 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8636 label = gtk_label_new(_("MIME type"));
8637 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8639 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8640 mimetype_entry = gtk_combo_box_entry_new_text();
8641 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8642 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8644 /* stuff with list */
8645 mime_type_list = procmime_get_mime_type_list();
8647 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8648 MimeType *type = (MimeType *) mime_type_list->data;
8651 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8653 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8656 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8657 (GCompareFunc)strcmp2);
8660 for (mime_type_list = strlist; mime_type_list != NULL;
8661 mime_type_list = mime_type_list->next) {
8662 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8663 g_free(mime_type_list->data);
8665 g_list_free(strlist);
8666 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8667 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8669 label = gtk_label_new(_("Encoding"));
8670 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8672 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8674 hbox = gtk_hbox_new(FALSE, 0);
8675 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8676 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8678 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8679 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8681 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8682 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8683 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8684 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8685 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8687 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8689 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8690 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8692 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8693 &ok_btn, GTK_STOCK_OK,
8695 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8696 gtk_widget_grab_default(ok_btn);
8698 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8699 G_CALLBACK(attach_property_ok),
8701 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8702 G_CALLBACK(attach_property_cancel),
8705 gtk_widget_show_all(vbox);
8707 attach_prop.window = window;
8708 attach_prop.mimetype_entry = mimetype_entry;
8709 attach_prop.encoding_optmenu = optmenu;
8710 attach_prop.path_entry = path_entry;
8711 attach_prop.filename_entry = filename_entry;
8712 attach_prop.ok_btn = ok_btn;
8713 attach_prop.cancel_btn = cancel_btn;
8716 #undef SET_LABEL_AND_ENTRY
8718 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8724 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8730 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8731 gboolean *cancelled)
8739 static gboolean attach_property_key_pressed(GtkWidget *widget,
8741 gboolean *cancelled)
8743 if (event && event->keyval == GDK_Escape) {
8747 if (event && event->keyval == GDK_Return) {
8755 static void compose_exec_ext_editor(Compose *compose)
8762 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8763 G_DIR_SEPARATOR, compose);
8765 if (pipe(pipe_fds) < 0) {
8771 if ((pid = fork()) < 0) {
8778 /* close the write side of the pipe */
8781 compose->exteditor_file = g_strdup(tmp);
8782 compose->exteditor_pid = pid;
8784 compose_set_ext_editor_sensitive(compose, FALSE);
8787 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8789 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8791 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8795 } else { /* process-monitoring process */
8801 /* close the read side of the pipe */
8804 if (compose_write_body_to_file(compose, tmp) < 0) {
8805 fd_write_all(pipe_fds[1], "2\n", 2);
8809 pid_ed = compose_exec_ext_editor_real(tmp);
8811 fd_write_all(pipe_fds[1], "1\n", 2);
8815 /* wait until editor is terminated */
8816 waitpid(pid_ed, NULL, 0);
8818 fd_write_all(pipe_fds[1], "0\n", 2);
8825 #endif /* G_OS_UNIX */
8829 static gint compose_exec_ext_editor_real(const gchar *file)
8836 cm_return_val_if_fail(file != NULL, -1);
8838 if ((pid = fork()) < 0) {
8843 if (pid != 0) return pid;
8845 /* grandchild process */
8847 if (setpgid(0, getppid()))
8850 if (prefs_common_get_ext_editor_cmd() &&
8851 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8852 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8853 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8855 if (prefs_common_get_ext_editor_cmd())
8856 g_warning("External editor command-line is invalid: '%s'\n",
8857 prefs_common_get_ext_editor_cmd());
8858 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8861 cmdline = strsplit_with_quote(buf, " ", 1024);
8862 execvp(cmdline[0], cmdline);
8865 g_strfreev(cmdline);
8870 static gboolean compose_ext_editor_kill(Compose *compose)
8872 pid_t pgid = compose->exteditor_pid * -1;
8875 ret = kill(pgid, 0);
8877 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8881 msg = g_strdup_printf
8882 (_("The external editor is still working.\n"
8883 "Force terminating the process?\n"
8884 "process group id: %d"), -pgid);
8885 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8886 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8890 if (val == G_ALERTALTERNATE) {
8891 g_source_remove(compose->exteditor_tag);
8892 g_io_channel_shutdown(compose->exteditor_ch,
8894 g_io_channel_unref(compose->exteditor_ch);
8896 if (kill(pgid, SIGTERM) < 0) perror("kill");
8897 waitpid(compose->exteditor_pid, NULL, 0);
8899 g_warning("Terminated process group id: %d", -pgid);
8900 g_warning("Temporary file: %s",
8901 compose->exteditor_file);
8903 compose_set_ext_editor_sensitive(compose, TRUE);
8905 g_free(compose->exteditor_file);
8906 compose->exteditor_file = NULL;
8907 compose->exteditor_pid = -1;
8908 compose->exteditor_ch = NULL;
8909 compose->exteditor_tag = -1;
8917 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8921 Compose *compose = (Compose *)data;
8924 debug_print(_("Compose: input from monitoring process\n"));
8926 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8928 g_io_channel_shutdown(source, FALSE, NULL);
8929 g_io_channel_unref(source);
8931 waitpid(compose->exteditor_pid, NULL, 0);
8933 if (buf[0] == '0') { /* success */
8934 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8935 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8937 gtk_text_buffer_set_text(buffer, "", -1);
8938 compose_insert_file(compose, compose->exteditor_file);
8939 compose_changed_cb(NULL, compose);
8940 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8942 if (claws_unlink(compose->exteditor_file) < 0)
8943 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8944 } else if (buf[0] == '1') { /* failed */
8945 g_warning("Couldn't exec external editor\n");
8946 if (claws_unlink(compose->exteditor_file) < 0)
8947 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8948 } else if (buf[0] == '2') {
8949 g_warning("Couldn't write to file\n");
8950 } else if (buf[0] == '3') {
8951 g_warning("Pipe read failed\n");
8954 compose_set_ext_editor_sensitive(compose, TRUE);
8956 g_free(compose->exteditor_file);
8957 compose->exteditor_file = NULL;
8958 compose->exteditor_pid = -1;
8959 compose->exteditor_ch = NULL;
8960 compose->exteditor_tag = -1;
8965 static void compose_set_ext_editor_sensitive(Compose *compose,
8968 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8969 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8970 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8972 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8973 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8974 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8976 gtk_widget_set_sensitive(compose->text, sensitive);
8977 if (compose->toolbar->send_btn)
8978 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8979 if (compose->toolbar->sendl_btn)
8980 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8981 if (compose->toolbar->draft_btn)
8982 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8983 if (compose->toolbar->insert_btn)
8984 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8985 if (compose->toolbar->sig_btn)
8986 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8987 if (compose->toolbar->exteditor_btn)
8988 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8989 if (compose->toolbar->linewrap_current_btn)
8990 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8991 if (compose->toolbar->linewrap_all_btn)
8992 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8994 #endif /* G_OS_UNIX */
8997 * compose_undo_state_changed:
8999 * Change the sensivity of the menuentries undo and redo
9001 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9002 gint redo_state, gpointer data)
9004 Compose *compose = (Compose *)data;
9006 switch (undo_state) {
9007 case UNDO_STATE_TRUE:
9008 if (!undostruct->undo_state) {
9009 undostruct->undo_state = TRUE;
9010 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9013 case UNDO_STATE_FALSE:
9014 if (undostruct->undo_state) {
9015 undostruct->undo_state = FALSE;
9016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9019 case UNDO_STATE_UNCHANGED:
9021 case UNDO_STATE_REFRESH:
9022 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9025 g_warning("Undo state not recognized");
9029 switch (redo_state) {
9030 case UNDO_STATE_TRUE:
9031 if (!undostruct->redo_state) {
9032 undostruct->redo_state = TRUE;
9033 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9036 case UNDO_STATE_FALSE:
9037 if (undostruct->redo_state) {
9038 undostruct->redo_state = FALSE;
9039 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9042 case UNDO_STATE_UNCHANGED:
9044 case UNDO_STATE_REFRESH:
9045 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9048 g_warning("Redo state not recognized");
9053 /* callback functions */
9055 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9056 * includes "non-client" (windows-izm) in calculation, so this calculation
9057 * may not be accurate.
9059 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9060 GtkAllocation *allocation,
9061 GtkSHRuler *shruler)
9063 if (prefs_common.show_ruler) {
9064 gint char_width = 0, char_height = 0;
9065 gint line_width_in_chars;
9067 gtkut_get_font_size(GTK_WIDGET(widget),
9068 &char_width, &char_height);
9069 line_width_in_chars =
9070 (allocation->width - allocation->x) / char_width;
9072 /* got the maximum */
9073 gtk_ruler_set_range(GTK_RULER(shruler),
9074 0.0, line_width_in_chars, 0,
9075 /*line_width_in_chars*/ char_width);
9084 ComposePrefType type;
9085 gboolean entry_marked;
9088 static void account_activated(GtkComboBox *optmenu, gpointer data)
9090 Compose *compose = (Compose *)data;
9093 gchar *folderidentifier;
9094 gint account_id = 0;
9097 GSList *list, *saved_list = NULL;
9098 HeaderEntryState *state;
9099 GtkRcStyle *style = NULL;
9100 static GdkColor yellow;
9101 static gboolean color_set = FALSE;
9103 /* Get ID of active account in the combo box */
9104 menu = gtk_combo_box_get_model(optmenu);
9105 gtk_combo_box_get_active_iter(optmenu, &iter);
9106 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9108 ac = account_find_from_id(account_id);
9109 cm_return_if_fail(ac != NULL);
9111 if (ac != compose->account) {
9112 compose_select_account(compose, ac, FALSE);
9114 for (list = compose->header_list; list; list = list->next) {
9115 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9117 if (hentry->type == PREF_ACCOUNT || !list->next) {
9118 compose_destroy_headerentry(compose, hentry);
9122 state = g_malloc0(sizeof(HeaderEntryState));
9123 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9124 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9125 state->entry = gtk_editable_get_chars(
9126 GTK_EDITABLE(hentry->entry), 0, -1);
9127 state->type = hentry->type;
9130 gdk_color_parse("#f5f6be", &yellow);
9131 color_set = gdk_colormap_alloc_color(
9132 gdk_colormap_get_system(),
9133 &yellow, FALSE, TRUE);
9136 style = gtk_widget_get_modifier_style(hentry->entry);
9137 state->entry_marked = gdk_color_equal(&yellow,
9138 &style->base[GTK_STATE_NORMAL]);
9140 saved_list = g_slist_append(saved_list, state);
9141 compose_destroy_headerentry(compose, hentry);
9144 compose->header_last = NULL;
9145 g_slist_free(compose->header_list);
9146 compose->header_list = NULL;
9147 compose->header_nextrow = 1;
9148 compose_create_header_entry(compose);
9150 if (ac->set_autocc && ac->auto_cc)
9151 compose_entry_append(compose, ac->auto_cc,
9152 COMPOSE_CC, PREF_ACCOUNT);
9154 if (ac->set_autobcc && ac->auto_bcc)
9155 compose_entry_append(compose, ac->auto_bcc,
9156 COMPOSE_BCC, PREF_ACCOUNT);
9158 if (ac->set_autoreplyto && ac->auto_replyto)
9159 compose_entry_append(compose, ac->auto_replyto,
9160 COMPOSE_REPLYTO, PREF_ACCOUNT);
9162 for (list = saved_list; list; list = list->next) {
9163 state = (HeaderEntryState *) list->data;
9165 compose_add_header_entry(compose, state->header,
9166 state->entry, state->type);
9167 if (state->entry_marked)
9168 compose_entry_mark_default_to(compose, state->entry);
9170 g_free(state->header);
9171 g_free(state->entry);
9174 g_slist_free(saved_list);
9176 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9177 (ac->protocol == A_NNTP) ?
9178 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9181 /* Set message save folder */
9182 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9183 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9185 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9186 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9188 compose_set_save_to(compose, NULL);
9189 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9190 folderidentifier = folder_item_get_identifier(account_get_special_folder
9191 (compose->account, F_OUTBOX));
9192 compose_set_save_to(compose, folderidentifier);
9193 g_free(folderidentifier);
9197 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9198 GtkTreeViewColumn *column, Compose *compose)
9200 compose_attach_property(NULL, compose);
9203 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9206 Compose *compose = (Compose *)data;
9207 GtkTreeSelection *attach_selection;
9208 gint attach_nr_selected;
9210 if (!event) return FALSE;
9212 if (event->button == 3) {
9213 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9214 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9216 if (attach_nr_selected > 0)
9218 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9219 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9221 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9222 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9225 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9226 NULL, NULL, event->button, event->time);
9233 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9236 Compose *compose = (Compose *)data;
9238 if (!event) return FALSE;
9240 switch (event->keyval) {
9242 compose_attach_remove_selected(NULL, compose);
9248 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9250 toolbar_comp_set_sensitive(compose, allow);
9251 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9252 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9254 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9256 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9257 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9258 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9260 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9264 static void compose_send_cb(GtkAction *action, gpointer data)
9266 Compose *compose = (Compose *)data;
9268 if (prefs_common.work_offline &&
9269 !inc_offline_should_override(TRUE,
9270 _("Claws Mail needs network access in order "
9271 "to send this email.")))
9274 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9275 g_source_remove(compose->draft_timeout_tag);
9276 compose->draft_timeout_tag = -1;
9279 compose_send(compose);
9282 static void compose_send_later_cb(GtkAction *action, gpointer data)
9284 Compose *compose = (Compose *)data;
9288 compose_allow_user_actions(compose, FALSE);
9289 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9290 compose_allow_user_actions(compose, TRUE);
9294 compose_close(compose);
9295 } else if (val == -1) {
9296 alertpanel_error(_("Could not queue message."));
9297 } else if (val == -2) {
9298 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9299 } else if (val == -3) {
9300 if (privacy_peek_error())
9301 alertpanel_error(_("Could not queue message for sending:\n\n"
9302 "Signature failed: %s"), privacy_get_error());
9303 } else if (val == -4) {
9304 alertpanel_error(_("Could not queue message for sending:\n\n"
9305 "Charset conversion failed."));
9306 } else if (val == -5) {
9307 alertpanel_error(_("Could not queue message for sending:\n\n"
9308 "Couldn't get recipient encryption key."));
9309 } else if (val == -6) {
9312 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9315 #define DRAFTED_AT_EXIT "drafted_at_exit"
9316 static void compose_register_draft(MsgInfo *info)
9318 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9319 DRAFTED_AT_EXIT, NULL);
9320 FILE *fp = g_fopen(filepath, "ab");
9323 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9331 gboolean compose_draft (gpointer data, guint action)
9333 Compose *compose = (Compose *)data;
9337 MsgFlags flag = {0, 0};
9338 static gboolean lock = FALSE;
9339 MsgInfo *newmsginfo;
9341 gboolean target_locked = FALSE;
9342 gboolean err = FALSE;
9344 if (lock) return FALSE;
9346 if (compose->sending)
9349 draft = account_get_special_folder(compose->account, F_DRAFT);
9350 cm_return_val_if_fail(draft != NULL, FALSE);
9352 if (!g_mutex_trylock(compose->mutex)) {
9353 /* we don't want to lock the mutex once it's available,
9354 * because as the only other part of compose.c locking
9355 * it is compose_close - which means once unlocked,
9356 * the compose struct will be freed */
9357 debug_print("couldn't lock mutex, probably sending\n");
9363 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9364 G_DIR_SEPARATOR, compose);
9365 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9366 FILE_OP_ERROR(tmp, "fopen");
9370 /* chmod for security */
9371 if (change_file_mode_rw(fp, tmp) < 0) {
9372 FILE_OP_ERROR(tmp, "chmod");
9373 g_warning("can't change file mode\n");
9376 /* Save draft infos */
9377 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9378 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9380 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9381 gchar *savefolderid;
9383 savefolderid = compose_get_save_to(compose);
9384 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9385 g_free(savefolderid);
9387 if (compose->return_receipt) {
9388 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9390 if (compose->privacy_system) {
9391 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9392 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9393 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9396 /* Message-ID of message replying to */
9397 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9400 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9401 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9404 /* Message-ID of message forwarding to */
9405 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9408 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9409 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9413 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9414 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9416 /* end of headers */
9417 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9424 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9428 if (fclose(fp) == EOF) {
9432 if (compose->targetinfo) {
9433 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9434 flag.perm_flags = target_locked?MSG_LOCKED:0;
9436 flag.tmp_flags = MSG_DRAFT;
9438 folder_item_scan(draft);
9439 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9440 MsgInfo *tmpinfo = NULL;
9441 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9442 if (compose->msgid) {
9443 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9446 msgnum = tmpinfo->msgnum;
9447 procmsg_msginfo_free(tmpinfo);
9448 debug_print("got draft msgnum %d from scanning\n", msgnum);
9450 debug_print("didn't get draft msgnum after scanning\n");
9453 debug_print("got draft msgnum %d from adding\n", msgnum);
9459 if (action != COMPOSE_AUTO_SAVE) {
9460 if (action != COMPOSE_DRAFT_FOR_EXIT)
9461 alertpanel_error(_("Could not save draft."));
9464 gtkut_window_popup(compose->window);
9465 val = alertpanel_full(_("Could not save draft"),
9466 _("Could not save draft.\n"
9467 "Do you want to cancel exit or discard this email?"),
9468 _("_Cancel exit"), _("_Discard email"), NULL,
9469 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9470 if (val == G_ALERTALTERNATE) {
9472 g_mutex_unlock(compose->mutex); /* must be done before closing */
9473 compose_close(compose);
9477 g_mutex_unlock(compose->mutex); /* must be done before closing */
9486 if (compose->mode == COMPOSE_REEDIT) {
9487 compose_remove_reedit_target(compose, TRUE);
9490 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9493 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9495 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9497 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9498 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9499 procmsg_msginfo_set_flags(newmsginfo, 0,
9500 MSG_HAS_ATTACHMENT);
9502 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9503 compose_register_draft(newmsginfo);
9505 procmsg_msginfo_free(newmsginfo);
9508 folder_item_scan(draft);
9510 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9512 g_mutex_unlock(compose->mutex); /* must be done before closing */
9513 compose_close(compose);
9519 path = folder_item_fetch_msg(draft, msgnum);
9521 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9524 if (g_stat(path, &s) < 0) {
9525 FILE_OP_ERROR(path, "stat");
9531 procmsg_msginfo_free(compose->targetinfo);
9532 compose->targetinfo = procmsg_msginfo_new();
9533 compose->targetinfo->msgnum = msgnum;
9534 compose->targetinfo->size = (goffset)s.st_size;
9535 compose->targetinfo->mtime = s.st_mtime;
9536 compose->targetinfo->folder = draft;
9538 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9539 compose->mode = COMPOSE_REEDIT;
9541 if (action == COMPOSE_AUTO_SAVE) {
9542 compose->autosaved_draft = compose->targetinfo;
9544 compose->modified = FALSE;
9545 compose_set_title(compose);
9549 g_mutex_unlock(compose->mutex);
9553 void compose_clear_exit_drafts(void)
9555 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9556 DRAFTED_AT_EXIT, NULL);
9557 if (is_file_exist(filepath))
9558 claws_unlink(filepath);
9563 void compose_reopen_exit_drafts(void)
9565 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9566 DRAFTED_AT_EXIT, NULL);
9567 FILE *fp = g_fopen(filepath, "rb");
9571 while (fgets(buf, sizeof(buf), fp)) {
9572 gchar **parts = g_strsplit(buf, "\t", 2);
9573 const gchar *folder = parts[0];
9574 int msgnum = parts[1] ? atoi(parts[1]):-1;
9576 if (folder && *folder && msgnum > -1) {
9577 FolderItem *item = folder_find_item_from_identifier(folder);
9578 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9580 compose_reedit(info, FALSE);
9587 compose_clear_exit_drafts();
9590 static void compose_save_cb(GtkAction *action, gpointer data)
9592 Compose *compose = (Compose *)data;
9593 compose_draft(compose, COMPOSE_KEEP_EDITING);
9594 compose->rmode = COMPOSE_REEDIT;
9597 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9599 if (compose && file_list) {
9602 for ( tmp = file_list; tmp; tmp = tmp->next) {
9603 gchar *file = (gchar *) tmp->data;
9604 gchar *utf8_filename = conv_filename_to_utf8(file);
9605 compose_attach_append(compose, file, utf8_filename, NULL);
9606 compose_changed_cb(NULL, compose);
9611 g_free(utf8_filename);
9616 static void compose_attach_cb(GtkAction *action, gpointer data)
9618 Compose *compose = (Compose *)data;
9621 if (compose->redirect_filename != NULL)
9624 file_list = filesel_select_multiple_files_open(_("Select file"));
9627 compose_attach_from_list(compose, file_list, TRUE);
9628 g_list_free(file_list);
9632 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9634 Compose *compose = (Compose *)data;
9636 gint files_inserted = 0;
9638 file_list = filesel_select_multiple_files_open(_("Select file"));
9643 for ( tmp = file_list; tmp; tmp = tmp->next) {
9644 gchar *file = (gchar *) tmp->data;
9645 gchar *filedup = g_strdup(file);
9646 gchar *shortfile = g_path_get_basename(filedup);
9647 ComposeInsertResult res;
9648 /* insert the file if the file is short or if the user confirmed that
9649 he/she wants to insert the large file */
9650 res = compose_insert_file(compose, file);
9651 if (res == COMPOSE_INSERT_READ_ERROR) {
9652 alertpanel_error(_("File '%s' could not be read."), shortfile);
9653 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9654 alertpanel_error(_("File '%s' contained invalid characters\n"
9655 "for the current encoding, insertion may be incorrect."),
9657 } else if (res == COMPOSE_INSERT_SUCCESS)
9664 g_list_free(file_list);
9668 if (files_inserted > 0 && compose->gtkaspell &&
9669 compose->gtkaspell->check_while_typing)
9670 gtkaspell_highlight_all(compose->gtkaspell);
9674 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9676 Compose *compose = (Compose *)data;
9678 compose_insert_sig(compose, FALSE);
9681 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9685 Compose *compose = (Compose *)data;
9687 gtkut_widget_get_uposition(widget, &x, &y);
9688 if (!compose->batch) {
9689 prefs_common.compose_x = x;
9690 prefs_common.compose_y = y;
9692 if (compose->sending || compose->updating)
9694 compose_close_cb(NULL, compose);
9698 void compose_close_toolbar(Compose *compose)
9700 compose_close_cb(NULL, compose);
9703 static void compose_close_cb(GtkAction *action, gpointer data)
9705 Compose *compose = (Compose *)data;
9709 if (compose->exteditor_tag != -1) {
9710 if (!compose_ext_editor_kill(compose))
9715 if (compose->modified) {
9716 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9717 if (!g_mutex_trylock(compose->mutex)) {
9718 /* we don't want to lock the mutex once it's available,
9719 * because as the only other part of compose.c locking
9720 * it is compose_close - which means once unlocked,
9721 * the compose struct will be freed */
9722 debug_print("couldn't lock mutex, probably sending\n");
9726 val = alertpanel(_("Discard message"),
9727 _("This message has been modified. Discard it?"),
9728 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9730 val = alertpanel(_("Save changes"),
9731 _("This message has been modified. Save the latest changes?"),
9732 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9734 g_mutex_unlock(compose->mutex);
9736 case G_ALERTDEFAULT:
9737 if (prefs_common.autosave && !reedit)
9738 compose_remove_draft(compose);
9740 case G_ALERTALTERNATE:
9741 compose_draft(data, COMPOSE_QUIT_EDITING);
9748 compose_close(compose);
9751 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9753 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9754 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9755 Compose *compose = (Compose *) data;
9758 compose->out_encoding = (CharSet)value;
9761 static void compose_address_cb(GtkAction *action, gpointer data)
9763 Compose *compose = (Compose *)data;
9765 addressbook_open(compose);
9768 static void about_show_cb(GtkAction *action, gpointer data)
9773 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9775 Compose *compose = (Compose *)data;
9780 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9781 cm_return_if_fail(tmpl != NULL);
9783 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9785 val = alertpanel(_("Apply template"), msg,
9786 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9789 if (val == G_ALERTDEFAULT)
9790 compose_template_apply(compose, tmpl, TRUE);
9791 else if (val == G_ALERTALTERNATE)
9792 compose_template_apply(compose, tmpl, FALSE);
9795 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9797 Compose *compose = (Compose *)data;
9799 compose_exec_ext_editor(compose);
9802 static void compose_undo_cb(GtkAction *action, gpointer data)
9804 Compose *compose = (Compose *)data;
9805 gboolean prev_autowrap = compose->autowrap;
9807 compose->autowrap = FALSE;
9808 undo_undo(compose->undostruct);
9809 compose->autowrap = prev_autowrap;
9812 static void compose_redo_cb(GtkAction *action, gpointer data)
9814 Compose *compose = (Compose *)data;
9815 gboolean prev_autowrap = compose->autowrap;
9817 compose->autowrap = FALSE;
9818 undo_redo(compose->undostruct);
9819 compose->autowrap = prev_autowrap;
9822 static void entry_cut_clipboard(GtkWidget *entry)
9824 if (GTK_IS_EDITABLE(entry))
9825 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9826 else if (GTK_IS_TEXT_VIEW(entry))
9827 gtk_text_buffer_cut_clipboard(
9828 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9829 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9833 static void entry_copy_clipboard(GtkWidget *entry)
9835 if (GTK_IS_EDITABLE(entry))
9836 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9837 else if (GTK_IS_TEXT_VIEW(entry))
9838 gtk_text_buffer_copy_clipboard(
9839 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9840 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9843 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9844 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9846 if (GTK_IS_TEXT_VIEW(entry)) {
9847 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9848 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9849 GtkTextIter start_iter, end_iter;
9851 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9853 if (contents == NULL)
9856 /* we shouldn't delete the selection when middle-click-pasting, or we
9857 * can't mid-click-paste our own selection */
9858 if (clip != GDK_SELECTION_PRIMARY) {
9859 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9860 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9863 if (insert_place == NULL) {
9864 /* if insert_place isn't specified, insert at the cursor.
9865 * used for Ctrl-V pasting */
9866 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9867 start = gtk_text_iter_get_offset(&start_iter);
9868 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9870 /* if insert_place is specified, paste here.
9871 * used for mid-click-pasting */
9872 start = gtk_text_iter_get_offset(insert_place);
9873 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9874 if (prefs_common.primary_paste_unselects)
9875 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9879 /* paste unwrapped: mark the paste so it's not wrapped later */
9880 end = start + strlen(contents);
9881 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9882 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9883 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9884 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9885 /* rewrap paragraph now (after a mid-click-paste) */
9886 mark_start = gtk_text_buffer_get_insert(buffer);
9887 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9888 gtk_text_iter_backward_char(&start_iter);
9889 compose_beautify_paragraph(compose, &start_iter, TRUE);
9891 } else if (GTK_IS_EDITABLE(entry))
9892 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9894 compose->modified = TRUE;
9897 static void entry_allsel(GtkWidget *entry)
9899 if (GTK_IS_EDITABLE(entry))
9900 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9901 else if (GTK_IS_TEXT_VIEW(entry)) {
9902 GtkTextIter startiter, enditer;
9903 GtkTextBuffer *textbuf;
9905 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9906 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9907 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9909 gtk_text_buffer_move_mark_by_name(textbuf,
9910 "selection_bound", &startiter);
9911 gtk_text_buffer_move_mark_by_name(textbuf,
9912 "insert", &enditer);
9916 static void compose_cut_cb(GtkAction *action, gpointer data)
9918 Compose *compose = (Compose *)data;
9919 if (compose->focused_editable
9920 #ifndef GENERIC_UMPC
9921 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9924 entry_cut_clipboard(compose->focused_editable);
9927 static void compose_copy_cb(GtkAction *action, gpointer data)
9929 Compose *compose = (Compose *)data;
9930 if (compose->focused_editable
9931 #ifndef GENERIC_UMPC
9932 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9935 entry_copy_clipboard(compose->focused_editable);
9938 static void compose_paste_cb(GtkAction *action, gpointer data)
9940 Compose *compose = (Compose *)data;
9942 GtkTextBuffer *buffer;
9944 if (compose->focused_editable &&
9945 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9946 entry_paste_clipboard(compose, compose->focused_editable,
9947 prefs_common.linewrap_pastes,
9948 GDK_SELECTION_CLIPBOARD, NULL);
9952 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9953 compose->gtkaspell &&
9954 compose->gtkaspell->check_while_typing)
9955 gtkaspell_highlight_all(compose->gtkaspell);
9959 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9961 Compose *compose = (Compose *)data;
9962 gint wrap_quote = prefs_common.linewrap_quote;
9963 if (compose->focused_editable
9964 #ifndef GENERIC_UMPC
9965 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9968 /* let text_insert() (called directly or at a later time
9969 * after the gtk_editable_paste_clipboard) know that
9970 * text is to be inserted as a quotation. implemented
9971 * by using a simple refcount... */
9972 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9973 G_OBJECT(compose->focused_editable),
9974 "paste_as_quotation"));
9975 g_object_set_data(G_OBJECT(compose->focused_editable),
9976 "paste_as_quotation",
9977 GINT_TO_POINTER(paste_as_quotation + 1));
9978 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9979 entry_paste_clipboard(compose, compose->focused_editable,
9980 prefs_common.linewrap_pastes,
9981 GDK_SELECTION_CLIPBOARD, NULL);
9982 prefs_common.linewrap_quote = wrap_quote;
9986 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9988 Compose *compose = (Compose *)data;
9990 GtkTextBuffer *buffer;
9992 if (compose->focused_editable
9993 #ifndef GENERIC_UMPC
9994 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9997 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9998 GDK_SELECTION_CLIPBOARD, NULL);
10002 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10003 compose->gtkaspell &&
10004 compose->gtkaspell->check_while_typing)
10005 gtkaspell_highlight_all(compose->gtkaspell);
10009 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10011 Compose *compose = (Compose *)data;
10012 gint prev_autowrap;
10013 GtkTextBuffer *buffer;
10015 if (compose->focused_editable
10016 #ifndef GENERIC_UMPC
10017 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10020 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10021 GDK_SELECTION_CLIPBOARD, NULL);
10025 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10026 compose->gtkaspell &&
10027 compose->gtkaspell->check_while_typing)
10028 gtkaspell_highlight_all(compose->gtkaspell);
10032 static void compose_allsel_cb(GtkAction *action, gpointer data)
10034 Compose *compose = (Compose *)data;
10035 if (compose->focused_editable
10036 #ifndef GENERIC_UMPC
10037 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10040 entry_allsel(compose->focused_editable);
10043 static void textview_move_beginning_of_line (GtkTextView *text)
10045 GtkTextBuffer *buffer;
10049 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10051 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10052 mark = gtk_text_buffer_get_insert(buffer);
10053 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10054 gtk_text_iter_set_line_offset(&ins, 0);
10055 gtk_text_buffer_place_cursor(buffer, &ins);
10058 static void textview_move_forward_character (GtkTextView *text)
10060 GtkTextBuffer *buffer;
10064 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10066 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10067 mark = gtk_text_buffer_get_insert(buffer);
10068 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10069 if (gtk_text_iter_forward_cursor_position(&ins))
10070 gtk_text_buffer_place_cursor(buffer, &ins);
10073 static void textview_move_backward_character (GtkTextView *text)
10075 GtkTextBuffer *buffer;
10079 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10081 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10082 mark = gtk_text_buffer_get_insert(buffer);
10083 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10084 if (gtk_text_iter_backward_cursor_position(&ins))
10085 gtk_text_buffer_place_cursor(buffer, &ins);
10088 static void textview_move_forward_word (GtkTextView *text)
10090 GtkTextBuffer *buffer;
10095 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10097 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10098 mark = gtk_text_buffer_get_insert(buffer);
10099 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10100 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10101 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10102 gtk_text_iter_backward_word_start(&ins);
10103 gtk_text_buffer_place_cursor(buffer, &ins);
10107 static void textview_move_backward_word (GtkTextView *text)
10109 GtkTextBuffer *buffer;
10114 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10116 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10117 mark = gtk_text_buffer_get_insert(buffer);
10118 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10119 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10120 if (gtk_text_iter_backward_word_starts(&ins, 1))
10121 gtk_text_buffer_place_cursor(buffer, &ins);
10124 static void textview_move_end_of_line (GtkTextView *text)
10126 GtkTextBuffer *buffer;
10130 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10132 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10133 mark = gtk_text_buffer_get_insert(buffer);
10134 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10135 if (gtk_text_iter_forward_to_line_end(&ins))
10136 gtk_text_buffer_place_cursor(buffer, &ins);
10139 static void textview_move_next_line (GtkTextView *text)
10141 GtkTextBuffer *buffer;
10146 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10149 mark = gtk_text_buffer_get_insert(buffer);
10150 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10151 offset = gtk_text_iter_get_line_offset(&ins);
10152 if (gtk_text_iter_forward_line(&ins)) {
10153 gtk_text_iter_set_line_offset(&ins, offset);
10154 gtk_text_buffer_place_cursor(buffer, &ins);
10158 static void textview_move_previous_line (GtkTextView *text)
10160 GtkTextBuffer *buffer;
10165 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10167 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10168 mark = gtk_text_buffer_get_insert(buffer);
10169 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10170 offset = gtk_text_iter_get_line_offset(&ins);
10171 if (gtk_text_iter_backward_line(&ins)) {
10172 gtk_text_iter_set_line_offset(&ins, offset);
10173 gtk_text_buffer_place_cursor(buffer, &ins);
10177 static void textview_delete_forward_character (GtkTextView *text)
10179 GtkTextBuffer *buffer;
10181 GtkTextIter ins, end_iter;
10183 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10185 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10186 mark = gtk_text_buffer_get_insert(buffer);
10187 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10189 if (gtk_text_iter_forward_char(&end_iter)) {
10190 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10194 static void textview_delete_backward_character (GtkTextView *text)
10196 GtkTextBuffer *buffer;
10198 GtkTextIter ins, end_iter;
10200 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10202 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10203 mark = gtk_text_buffer_get_insert(buffer);
10204 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10206 if (gtk_text_iter_backward_char(&end_iter)) {
10207 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10211 static void textview_delete_forward_word (GtkTextView *text)
10213 GtkTextBuffer *buffer;
10215 GtkTextIter ins, end_iter;
10217 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10219 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10220 mark = gtk_text_buffer_get_insert(buffer);
10221 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10223 if (gtk_text_iter_forward_word_end(&end_iter)) {
10224 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10228 static void textview_delete_backward_word (GtkTextView *text)
10230 GtkTextBuffer *buffer;
10232 GtkTextIter ins, end_iter;
10234 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10236 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10237 mark = gtk_text_buffer_get_insert(buffer);
10238 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10240 if (gtk_text_iter_backward_word_start(&end_iter)) {
10241 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10245 static void textview_delete_line (GtkTextView *text)
10247 GtkTextBuffer *buffer;
10249 GtkTextIter ins, start_iter, end_iter;
10251 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10253 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10254 mark = gtk_text_buffer_get_insert(buffer);
10255 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10258 gtk_text_iter_set_line_offset(&start_iter, 0);
10261 if (gtk_text_iter_ends_line(&end_iter)){
10262 if (!gtk_text_iter_forward_char(&end_iter))
10263 gtk_text_iter_backward_char(&start_iter);
10266 gtk_text_iter_forward_to_line_end(&end_iter);
10267 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10270 static void textview_delete_to_line_end (GtkTextView *text)
10272 GtkTextBuffer *buffer;
10274 GtkTextIter ins, end_iter;
10276 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10278 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10279 mark = gtk_text_buffer_get_insert(buffer);
10280 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10282 if (gtk_text_iter_ends_line(&end_iter))
10283 gtk_text_iter_forward_char(&end_iter);
10285 gtk_text_iter_forward_to_line_end(&end_iter);
10286 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10289 #define DO_ACTION(name, act) { \
10290 if(!strcmp(name, a_name)) { \
10294 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10296 const gchar *a_name = gtk_action_get_name(action);
10297 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10298 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10299 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10300 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10301 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10302 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10303 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10304 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10305 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10306 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10307 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10308 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10309 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10310 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10314 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10316 Compose *compose = (Compose *)data;
10317 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10318 ComposeCallAdvancedAction action = -1;
10320 action = compose_call_advanced_action_from_path(gaction);
10323 void (*do_action) (GtkTextView *text);
10324 } action_table[] = {
10325 {textview_move_beginning_of_line},
10326 {textview_move_forward_character},
10327 {textview_move_backward_character},
10328 {textview_move_forward_word},
10329 {textview_move_backward_word},
10330 {textview_move_end_of_line},
10331 {textview_move_next_line},
10332 {textview_move_previous_line},
10333 {textview_delete_forward_character},
10334 {textview_delete_backward_character},
10335 {textview_delete_forward_word},
10336 {textview_delete_backward_word},
10337 {textview_delete_line},
10338 {textview_delete_to_line_end}
10341 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10343 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10344 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10345 if (action_table[action].do_action)
10346 action_table[action].do_action(text);
10348 g_warning("Not implemented yet.");
10352 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10356 if (GTK_IS_EDITABLE(widget)) {
10357 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10358 gtk_editable_set_position(GTK_EDITABLE(widget),
10361 if (widget->parent && widget->parent->parent
10362 && widget->parent->parent->parent) {
10363 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10364 gint y = widget->allocation.y;
10365 gint height = widget->allocation.height;
10366 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10367 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10369 if (y < (int)shown->value) {
10370 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10372 if (y + height > (int)shown->value + (int)shown->page_size) {
10373 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10374 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10375 y + height - (int)shown->page_size - 1);
10377 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10378 (int)shown->upper - (int)shown->page_size - 1);
10385 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10386 compose->focused_editable = widget;
10388 #ifdef GENERIC_UMPC
10389 if (GTK_IS_TEXT_VIEW(widget)
10390 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10391 g_object_ref(compose->notebook);
10392 g_object_ref(compose->edit_vbox);
10393 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10394 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10395 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10396 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10397 g_object_unref(compose->notebook);
10398 g_object_unref(compose->edit_vbox);
10399 g_signal_handlers_block_by_func(G_OBJECT(widget),
10400 G_CALLBACK(compose_grab_focus_cb),
10402 gtk_widget_grab_focus(widget);
10403 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10404 G_CALLBACK(compose_grab_focus_cb),
10406 } else if (!GTK_IS_TEXT_VIEW(widget)
10407 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10408 g_object_ref(compose->notebook);
10409 g_object_ref(compose->edit_vbox);
10410 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10411 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10412 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10413 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10414 g_object_unref(compose->notebook);
10415 g_object_unref(compose->edit_vbox);
10416 g_signal_handlers_block_by_func(G_OBJECT(widget),
10417 G_CALLBACK(compose_grab_focus_cb),
10419 gtk_widget_grab_focus(widget);
10420 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10421 G_CALLBACK(compose_grab_focus_cb),
10427 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10429 compose->modified = TRUE;
10430 // compose_beautify_paragraph(compose, NULL, TRUE);
10431 #ifndef GENERIC_UMPC
10432 compose_set_title(compose);
10436 static void compose_wrap_cb(GtkAction *action, gpointer data)
10438 Compose *compose = (Compose *)data;
10439 compose_beautify_paragraph(compose, NULL, TRUE);
10442 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10444 Compose *compose = (Compose *)data;
10445 compose_wrap_all_full(compose, TRUE);
10448 static void compose_find_cb(GtkAction *action, gpointer data)
10450 Compose *compose = (Compose *)data;
10452 message_search_compose(compose);
10455 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10458 Compose *compose = (Compose *)data;
10459 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10460 if (compose->autowrap)
10461 compose_wrap_all_full(compose, TRUE);
10462 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10465 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10468 Compose *compose = (Compose *)data;
10469 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10472 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10474 Compose *compose = (Compose *)data;
10476 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10479 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10481 Compose *compose = (Compose *)data;
10483 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10486 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10488 g_free(compose->privacy_system);
10490 compose->privacy_system = g_strdup(account->default_privacy_system);
10491 compose_update_privacy_system_menu_item(compose, warn);
10494 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10496 Compose *compose = (Compose *)data;
10498 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10499 gtk_widget_show(compose->ruler_hbox);
10500 prefs_common.show_ruler = TRUE;
10502 gtk_widget_hide(compose->ruler_hbox);
10503 gtk_widget_queue_resize(compose->edit_vbox);
10504 prefs_common.show_ruler = FALSE;
10508 static void compose_attach_drag_received_cb (GtkWidget *widget,
10509 GdkDragContext *context,
10512 GtkSelectionData *data,
10515 gpointer user_data)
10517 Compose *compose = (Compose *)user_data;
10520 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10522 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10524 ) && gtk_drag_get_source_widget(context) !=
10525 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10526 list = uri_list_extract_filenames((const gchar *)data->data);
10527 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10528 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10529 compose_attach_append
10530 (compose, (const gchar *)tmp->data,
10531 utf8_filename, NULL);
10532 g_free(utf8_filename);
10534 if (list) compose_changed_cb(NULL, compose);
10535 list_free_strings(list);
10537 } else if (gtk_drag_get_source_widget(context)
10538 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10539 /* comes from our summaryview */
10540 SummaryView * summaryview = NULL;
10541 GSList * list = NULL, *cur = NULL;
10543 if (mainwindow_get_mainwindow())
10544 summaryview = mainwindow_get_mainwindow()->summaryview;
10547 list = summary_get_selected_msg_list(summaryview);
10549 for (cur = list; cur; cur = cur->next) {
10550 MsgInfo *msginfo = (MsgInfo *)cur->data;
10551 gchar *file = NULL;
10553 file = procmsg_get_message_file_full(msginfo,
10556 compose_attach_append(compose, (const gchar *)file,
10557 (const gchar *)file, "message/rfc822");
10561 g_slist_free(list);
10565 static gboolean compose_drag_drop(GtkWidget *widget,
10566 GdkDragContext *drag_context,
10568 guint time, gpointer user_data)
10570 /* not handling this signal makes compose_insert_drag_received_cb
10575 static void compose_insert_drag_received_cb (GtkWidget *widget,
10576 GdkDragContext *drag_context,
10579 GtkSelectionData *data,
10582 gpointer user_data)
10584 Compose *compose = (Compose *)user_data;
10587 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10590 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10592 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10594 AlertValue val = G_ALERTDEFAULT;
10596 list = uri_list_extract_filenames((const gchar *)data->data);
10597 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10598 /* Assume a list of no files, and data has ://, is a remote link */
10599 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10600 gchar *tmpfile = get_tmp_file();
10601 str_write_to_file(tmpdata, tmpfile);
10603 compose_insert_file(compose, tmpfile);
10604 claws_unlink(tmpfile);
10606 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10607 compose_beautify_paragraph(compose, NULL, TRUE);
10610 switch (prefs_common.compose_dnd_mode) {
10611 case COMPOSE_DND_ASK:
10612 val = alertpanel_full(_("Insert or attach?"),
10613 _("Do you want to insert the contents of the file(s) "
10614 "into the message body, or attach it to the email?"),
10615 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10616 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10618 case COMPOSE_DND_INSERT:
10619 val = G_ALERTALTERNATE;
10621 case COMPOSE_DND_ATTACH:
10622 val = G_ALERTOTHER;
10625 /* unexpected case */
10626 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10629 if (val & G_ALERTDISABLE) {
10630 val &= ~G_ALERTDISABLE;
10631 /* remember what action to perform by default, only if we don't click Cancel */
10632 if (val == G_ALERTALTERNATE)
10633 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10634 else if (val == G_ALERTOTHER)
10635 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10638 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10639 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10640 list_free_strings(list);
10643 } else if (val == G_ALERTOTHER) {
10644 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10645 list_free_strings(list);
10650 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10651 compose_insert_file(compose, (const gchar *)tmp->data);
10653 list_free_strings(list);
10655 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10660 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10663 static void compose_header_drag_received_cb (GtkWidget *widget,
10664 GdkDragContext *drag_context,
10667 GtkSelectionData *data,
10670 gpointer user_data)
10672 GtkEditable *entry = (GtkEditable *)user_data;
10673 gchar *email = (gchar *)data->data;
10675 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10678 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10679 gchar *decoded=g_new(gchar, strlen(email));
10682 email += strlen("mailto:");
10683 decode_uri(decoded, email); /* will fit */
10684 gtk_editable_delete_text(entry, 0, -1);
10685 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10686 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10690 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10693 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10695 Compose *compose = (Compose *)data;
10697 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10698 compose->return_receipt = TRUE;
10700 compose->return_receipt = FALSE;
10703 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10705 Compose *compose = (Compose *)data;
10707 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10708 compose->remove_references = TRUE;
10710 compose->remove_references = FALSE;
10713 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10714 ComposeHeaderEntry *headerentry)
10716 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10720 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10721 GdkEventKey *event,
10722 ComposeHeaderEntry *headerentry)
10724 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10725 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10726 !(event->state & GDK_MODIFIER_MASK) &&
10727 (event->keyval == GDK_BackSpace) &&
10728 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10729 gtk_container_remove
10730 (GTK_CONTAINER(headerentry->compose->header_table),
10731 headerentry->combo);
10732 gtk_container_remove
10733 (GTK_CONTAINER(headerentry->compose->header_table),
10734 headerentry->entry);
10735 headerentry->compose->header_list =
10736 g_slist_remove(headerentry->compose->header_list,
10738 g_free(headerentry);
10739 } else if (event->keyval == GDK_Tab) {
10740 if (headerentry->compose->header_last == headerentry) {
10741 /* Override default next focus, and give it to subject_entry
10742 * instead of notebook tabs
10744 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10745 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10752 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10753 ComposeHeaderEntry *headerentry)
10755 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10756 compose_create_header_entry(headerentry->compose);
10757 g_signal_handlers_disconnect_matched
10758 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10759 0, 0, NULL, NULL, headerentry);
10761 /* Automatically scroll down */
10762 GTK_EVENTS_FLUSH();
10763 compose_show_first_last_header(headerentry->compose, FALSE);
10769 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10771 GtkAdjustment *vadj;
10773 cm_return_if_fail(compose);
10774 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10775 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10776 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10777 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10778 gtk_adjustment_changed(vadj);
10781 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10782 const gchar *text, gint len, Compose *compose)
10784 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10785 (G_OBJECT(compose->text), "paste_as_quotation"));
10788 cm_return_if_fail(text != NULL);
10790 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10791 G_CALLBACK(text_inserted),
10793 if (paste_as_quotation) {
10795 const gchar *qmark;
10797 GtkTextIter start_iter;
10800 len = strlen(text);
10802 new_text = g_strndup(text, len);
10804 qmark = compose_quote_char_from_context(compose);
10806 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10807 gtk_text_buffer_place_cursor(buffer, iter);
10809 pos = gtk_text_iter_get_offset(iter);
10811 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10812 _("Quote format error at line %d."));
10813 quote_fmt_reset_vartable();
10815 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10816 GINT_TO_POINTER(paste_as_quotation - 1));
10818 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10819 gtk_text_buffer_place_cursor(buffer, iter);
10820 gtk_text_buffer_delete_mark(buffer, mark);
10822 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10823 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10824 compose_beautify_paragraph(compose, &start_iter, FALSE);
10825 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10826 gtk_text_buffer_delete_mark(buffer, mark);
10828 if (strcmp(text, "\n") || compose->automatic_break
10829 || gtk_text_iter_starts_line(iter)) {
10830 GtkTextIter before_ins;
10831 gtk_text_buffer_insert(buffer, iter, text, len);
10832 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10833 before_ins = *iter;
10834 gtk_text_iter_backward_chars(&before_ins, len);
10835 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10838 /* check if the preceding is just whitespace or quote */
10839 GtkTextIter start_line;
10840 gchar *tmp = NULL, *quote = NULL;
10841 gint quote_len = 0, is_normal = 0;
10842 start_line = *iter;
10843 gtk_text_iter_set_line_offset(&start_line, 0);
10844 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10847 if (*tmp == '\0') {
10850 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10858 gtk_text_buffer_insert(buffer, iter, text, len);
10860 gtk_text_buffer_insert_with_tags_by_name(buffer,
10861 iter, text, len, "no_join", NULL);
10866 if (!paste_as_quotation) {
10867 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10868 compose_beautify_paragraph(compose, iter, FALSE);
10869 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10870 gtk_text_buffer_delete_mark(buffer, mark);
10873 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10874 G_CALLBACK(text_inserted),
10876 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10878 if (prefs_common.autosave &&
10879 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10880 compose->draft_timeout_tag != -2 /* disabled while loading */)
10881 compose->draft_timeout_tag = g_timeout_add
10882 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10884 static gint compose_defer_auto_save_draft(Compose *compose)
10886 compose->draft_timeout_tag = -1;
10887 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10892 static void compose_check_all(GtkAction *action, gpointer data)
10894 Compose *compose = (Compose *)data;
10895 if (!compose->gtkaspell)
10898 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10899 claws_spell_entry_check_all(
10900 CLAWS_SPELL_ENTRY(compose->subject_entry));
10902 gtkaspell_check_all(compose->gtkaspell);
10905 static void compose_highlight_all(GtkAction *action, gpointer data)
10907 Compose *compose = (Compose *)data;
10908 if (compose->gtkaspell) {
10909 claws_spell_entry_recheck_all(
10910 CLAWS_SPELL_ENTRY(compose->subject_entry));
10911 gtkaspell_highlight_all(compose->gtkaspell);
10915 static void compose_check_backwards(GtkAction *action, gpointer data)
10917 Compose *compose = (Compose *)data;
10918 if (!compose->gtkaspell) {
10919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10923 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10924 claws_spell_entry_check_backwards(
10925 CLAWS_SPELL_ENTRY(compose->subject_entry));
10927 gtkaspell_check_backwards(compose->gtkaspell);
10930 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10932 Compose *compose = (Compose *)data;
10933 if (!compose->gtkaspell) {
10934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10938 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10939 claws_spell_entry_check_forwards_go(
10940 CLAWS_SPELL_ENTRY(compose->subject_entry));
10942 gtkaspell_check_forwards_go(compose->gtkaspell);
10947 *\brief Guess originating forward account from MsgInfo and several
10948 * "common preference" settings. Return NULL if no guess.
10950 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10952 PrefsAccount *account = NULL;
10954 cm_return_val_if_fail(msginfo, NULL);
10955 cm_return_val_if_fail(msginfo->folder, NULL);
10956 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10958 if (msginfo->folder->prefs->enable_default_account)
10959 account = account_find_from_id(msginfo->folder->prefs->default_account);
10962 account = msginfo->folder->folder->account;
10964 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10966 Xstrdup_a(to, msginfo->to, return NULL);
10967 extract_address(to);
10968 account = account_find_from_address(to, FALSE);
10971 if (!account && prefs_common.forward_account_autosel) {
10972 gchar cc[BUFFSIZE];
10973 if (!procheader_get_header_from_msginfo
10974 (msginfo, cc,sizeof cc , "Cc:")) {
10975 gchar *buf = cc + strlen("Cc:");
10976 extract_address(buf);
10977 account = account_find_from_address(buf, FALSE);
10981 if (!account && prefs_common.forward_account_autosel) {
10982 gchar deliveredto[BUFFSIZE];
10983 if (!procheader_get_header_from_msginfo
10984 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10985 gchar *buf = deliveredto + strlen("Delivered-To:");
10986 extract_address(buf);
10987 account = account_find_from_address(buf, FALSE);
10994 gboolean compose_close(Compose *compose)
10998 if (!g_mutex_trylock(compose->mutex)) {
10999 /* we have to wait for the (possibly deferred by auto-save)
11000 * drafting to be done, before destroying the compose under
11002 debug_print("waiting for drafting to finish...\n");
11003 compose_allow_user_actions(compose, FALSE);
11004 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11007 cm_return_val_if_fail(compose, FALSE);
11008 gtkut_widget_get_uposition(compose->window, &x, &y);
11009 if (!compose->batch) {
11010 prefs_common.compose_x = x;
11011 prefs_common.compose_y = y;
11013 g_mutex_unlock(compose->mutex);
11014 compose_destroy(compose);
11019 * Add entry field for each address in list.
11020 * \param compose E-Mail composition object.
11021 * \param listAddress List of (formatted) E-Mail addresses.
11023 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11026 node = listAddress;
11028 addr = ( gchar * ) node->data;
11029 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11030 node = g_list_next( node );
11034 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11035 guint action, gboolean opening_multiple)
11037 gchar *body = NULL;
11038 GSList *new_msglist = NULL;
11039 MsgInfo *tmp_msginfo = NULL;
11040 gboolean originally_enc = FALSE;
11041 gboolean originally_sig = FALSE;
11042 Compose *compose = NULL;
11043 gchar *s_system = NULL;
11045 cm_return_if_fail(msgview != NULL);
11047 cm_return_if_fail(msginfo_list != NULL);
11049 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11050 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11051 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11053 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11054 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11055 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11056 orig_msginfo, mimeinfo);
11057 if (tmp_msginfo != NULL) {
11058 new_msglist = g_slist_append(NULL, tmp_msginfo);
11060 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11061 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11062 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11064 tmp_msginfo->folder = orig_msginfo->folder;
11065 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11066 if (orig_msginfo->tags) {
11067 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11068 tmp_msginfo->folder->tags_dirty = TRUE;
11074 if (!opening_multiple)
11075 body = messageview_get_selection(msgview);
11078 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11079 procmsg_msginfo_free(tmp_msginfo);
11080 g_slist_free(new_msglist);
11082 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11084 if (compose && originally_enc) {
11085 compose_force_encryption(compose, compose->account, FALSE, s_system);
11088 if (compose && originally_sig && compose->account->default_sign_reply) {
11089 compose_force_signing(compose, compose->account, s_system);
11093 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11096 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11099 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11100 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11101 GSList *cur = msginfo_list;
11102 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11103 "messages. Opening the windows "
11104 "could take some time. Do you "
11105 "want to continue?"),
11106 g_slist_length(msginfo_list));
11107 if (g_slist_length(msginfo_list) > 9
11108 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11109 != G_ALERTALTERNATE) {
11114 /* We'll open multiple compose windows */
11115 /* let the WM place the next windows */
11116 compose_force_window_origin = FALSE;
11117 for (; cur; cur = cur->next) {
11119 tmplist.data = cur->data;
11120 tmplist.next = NULL;
11121 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11123 compose_force_window_origin = TRUE;
11125 /* forwarding multiple mails as attachments is done via a
11126 * single compose window */
11127 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11131 void compose_set_position(Compose *compose, gint pos)
11133 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11135 gtkut_text_view_set_position(text, pos);
11138 gboolean compose_search_string(Compose *compose,
11139 const gchar *str, gboolean case_sens)
11141 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11143 return gtkut_text_view_search_string(text, str, case_sens);
11146 gboolean compose_search_string_backward(Compose *compose,
11147 const gchar *str, gboolean case_sens)
11149 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11151 return gtkut_text_view_search_string_backward(text, str, case_sens);
11154 /* allocate a msginfo structure and populate its data from a compose data structure */
11155 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11157 MsgInfo *newmsginfo;
11159 gchar buf[BUFFSIZE];
11161 cm_return_val_if_fail( compose != NULL, NULL );
11163 newmsginfo = procmsg_msginfo_new();
11166 get_rfc822_date(buf, sizeof(buf));
11167 newmsginfo->date = g_strdup(buf);
11170 if (compose->from_name) {
11171 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11172 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11176 if (compose->subject_entry)
11177 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11179 /* to, cc, reply-to, newsgroups */
11180 for (list = compose->header_list; list; list = list->next) {
11181 gchar *header = gtk_editable_get_chars(
11183 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11184 gchar *entry = gtk_editable_get_chars(
11185 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11187 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11188 if ( newmsginfo->to == NULL ) {
11189 newmsginfo->to = g_strdup(entry);
11190 } else if (entry && *entry) {
11191 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11192 g_free(newmsginfo->to);
11193 newmsginfo->to = tmp;
11196 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11197 if ( newmsginfo->cc == NULL ) {
11198 newmsginfo->cc = g_strdup(entry);
11199 } else if (entry && *entry) {
11200 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11201 g_free(newmsginfo->cc);
11202 newmsginfo->cc = tmp;
11205 if ( strcasecmp(header,
11206 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11207 if ( newmsginfo->newsgroups == NULL ) {
11208 newmsginfo->newsgroups = g_strdup(entry);
11209 } else if (entry && *entry) {
11210 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11211 g_free(newmsginfo->newsgroups);
11212 newmsginfo->newsgroups = tmp;
11220 /* other data is unset */
11226 /* update compose's dictionaries from folder dict settings */
11227 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11228 FolderItem *folder_item)
11230 cm_return_if_fail(compose != NULL);
11232 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11233 FolderItemPrefs *prefs = folder_item->prefs;
11235 if (prefs->enable_default_dictionary)
11236 gtkaspell_change_dict(compose->gtkaspell,
11237 prefs->default_dictionary, FALSE);
11238 if (folder_item->prefs->enable_default_alt_dictionary)
11239 gtkaspell_change_alt_dict(compose->gtkaspell,
11240 prefs->default_alt_dictionary);
11241 if (prefs->enable_default_dictionary
11242 || prefs->enable_default_alt_dictionary)
11243 compose_spell_menu_changed(compose);