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_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
714 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
715 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
716 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
723 static GtkTargetEntry compose_mime_types[] =
725 {"text/uri-list", 0, 0},
726 {"UTF8_STRING", 0, 0},
730 static gboolean compose_put_existing_to_front(MsgInfo *info)
732 GList *compose_list = compose_get_compose_list();
736 for (elem = compose_list; elem != NULL && elem->data != NULL;
738 Compose *c = (Compose*)elem->data;
740 if (!c->targetinfo || !c->targetinfo->msgid ||
744 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
745 gtkut_window_popup(c->window);
753 static GdkColor quote_color1 =
754 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
755 static GdkColor quote_color2 =
756 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
757 static GdkColor quote_color3 =
758 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
760 static GdkColor quote_bgcolor1 =
761 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
762 static GdkColor quote_bgcolor2 =
763 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
764 static GdkColor quote_bgcolor3 =
765 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
767 static GdkColor signature_color = {
774 static GdkColor uri_color = {
781 static void compose_create_tags(GtkTextView *text, Compose *compose)
783 GtkTextBuffer *buffer;
784 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 buffer = gtk_text_view_get_buffer(text);
792 if (prefs_common.enable_color) {
793 /* grab the quote colors, converting from an int to a GdkColor */
794 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
796 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
798 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
800 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
802 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
804 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
806 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
808 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
811 signature_color = quote_color1 = quote_color2 = quote_color3 =
812 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
815 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
816 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
817 "foreground-gdk", "e_color1,
818 "paragraph-background-gdk", "e_bgcolor1,
820 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
821 "foreground-gdk", "e_color2,
822 "paragraph-background-gdk", "e_bgcolor2,
824 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
825 "foreground-gdk", "e_color3,
826 "paragraph-background-gdk", "e_bgcolor3,
829 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
830 "foreground-gdk", "e_color1,
832 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
833 "foreground-gdk", "e_color2,
835 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
836 "foreground-gdk", "e_color3,
840 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
841 "foreground-gdk", &signature_color,
844 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
845 "foreground-gdk", &uri_color,
847 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
848 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
850 color[0] = quote_color1;
851 color[1] = quote_color2;
852 color[2] = quote_color3;
853 color[3] = quote_bgcolor1;
854 color[4] = quote_bgcolor2;
855 color[5] = quote_bgcolor3;
856 color[6] = signature_color;
857 color[7] = uri_color;
858 cmap = gdk_drawable_get_colormap(compose->window->window);
859 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
861 for (i = 0; i < 8; i++) {
862 if (success[i] == FALSE) {
865 g_warning("Compose: color allocation failed.\n");
866 style = gtk_widget_get_style(GTK_WIDGET(text));
867 quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
869 signature_color = uri_color = black;
874 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
875 GPtrArray *attach_files)
877 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
880 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
882 return compose_generic_new(account, mailto, item, NULL, NULL);
885 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
887 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
890 #define SCROLL_TO_CURSOR(compose) { \
891 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
892 gtk_text_view_get_buffer( \
893 GTK_TEXT_VIEW(compose->text))); \
894 gtk_text_view_scroll_mark_onscreen( \
895 GTK_TEXT_VIEW(compose->text), \
899 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
902 if (folderidentifier) {
903 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
904 prefs_common.compose_save_to_history = add_history(
905 prefs_common.compose_save_to_history, folderidentifier);
906 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
907 prefs_common.compose_save_to_history);
910 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
911 if (folderidentifier)
912 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
914 gtk_entry_set_text(GTK_ENTRY(entry), "");
917 static gchar *compose_get_save_to(Compose *compose)
920 gchar *result = NULL;
921 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
922 result = gtk_editable_get_chars(entry, 0, -1);
925 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
926 prefs_common.compose_save_to_history = add_history(
927 prefs_common.compose_save_to_history, result);
928 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
929 prefs_common.compose_save_to_history);
934 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
935 GPtrArray *attach_files, GList *listAddress )
938 GtkTextView *textview;
939 GtkTextBuffer *textbuf;
941 const gchar *subject_format = NULL;
942 const gchar *body_format = NULL;
943 gchar *mailto_from = NULL;
944 PrefsAccount *mailto_account = NULL;
945 MsgInfo* dummyinfo = NULL;
946 MailField mfield = NO_FIELD_PRESENT;
950 /* check if mailto defines a from */
951 if (mailto && *mailto != '\0') {
952 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
953 /* mailto defines a from, check if we can get account prefs from it,
954 if not, the account prefs will be guessed using other ways, but we'll keep
957 mailto_account = account_find_from_address(mailto_from, TRUE);
959 account = mailto_account;
962 /* if no account prefs set from mailto, set if from folder prefs (if any) */
963 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
964 account = account_find_from_id(item->prefs->default_account);
966 /* if no account prefs set, fallback to the current one */
967 if (!account) account = cur_account;
968 cm_return_val_if_fail(account != NULL, NULL);
970 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
972 /* override from name if mailto asked for it */
974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
977 /* override from name according to folder properties */
978 if (item && item->prefs &&
979 item->prefs->compose_with_format &&
980 item->prefs->compose_override_from_format &&
981 *item->prefs->compose_override_from_format != '\0') {
986 dummyinfo = compose_msginfo_new_from_compose(compose);
988 /* decode \-escape sequences in the internal representation of the quote format */
989 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
990 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
993 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
996 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
998 quote_fmt_scan_string(tmp);
1001 buf = quote_fmt_get_buffer();
1003 alertpanel_error(_("New message From format error."));
1005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1006 quote_fmt_reset_vartable();
1011 compose->replyinfo = NULL;
1012 compose->fwdinfo = NULL;
1014 textview = GTK_TEXT_VIEW(compose->text);
1015 textbuf = gtk_text_view_get_buffer(textview);
1016 compose_create_tags(textview, compose);
1018 undo_block(compose->undostruct);
1020 compose_set_dictionaries_from_folder_prefs(compose, item);
1023 if (account->auto_sig)
1024 compose_insert_sig(compose, FALSE);
1025 gtk_text_buffer_get_start_iter(textbuf, &iter);
1026 gtk_text_buffer_place_cursor(textbuf, &iter);
1028 if (account->protocol != A_NNTP) {
1029 if (mailto && *mailto != '\0') {
1030 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1032 } else if (item && item->prefs) {
1033 if (item->prefs->enable_default_bcc) {
1034 compose_entry_append(compose, item->prefs->default_bcc,
1035 COMPOSE_BCC, PREF_FOLDER);
1037 if (item->prefs->enable_default_cc) {
1038 compose_entry_append(compose, item->prefs->default_cc,
1039 COMPOSE_CC, PREF_FOLDER);
1041 if (item->prefs->enable_default_replyto) {
1042 compose_entry_append(compose, item->prefs->default_replyto,
1043 COMPOSE_REPLYTO, PREF_FOLDER);
1045 if (item->prefs->enable_default_to) {
1046 compose_entry_append(compose, item->prefs->default_to,
1047 COMPOSE_TO, PREF_FOLDER);
1048 compose_entry_mark_default_to(compose, item->prefs->default_to);
1051 if (item && item->ret_rcpt) {
1052 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1055 if (mailto && *mailto != '\0') {
1056 if (!strchr(mailto, '@'))
1057 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1059 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1060 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1061 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1062 mfield = TO_FIELD_PRESENT;
1065 * CLAWS: just don't allow return receipt request, even if the user
1066 * may want to send an email. simple but foolproof.
1068 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1070 compose_add_field_list( compose, listAddress );
1072 if (item && item->prefs && item->prefs->compose_with_format) {
1073 subject_format = item->prefs->compose_subject_format;
1074 body_format = item->prefs->compose_body_format;
1075 } else if (account->compose_with_format) {
1076 subject_format = account->compose_subject_format;
1077 body_format = account->compose_body_format;
1078 } else if (prefs_common.compose_with_format) {
1079 subject_format = prefs_common.compose_subject_format;
1080 body_format = prefs_common.compose_body_format;
1083 if (subject_format || body_format) {
1086 && *subject_format != '\0' )
1088 gchar *subject = NULL;
1093 dummyinfo = compose_msginfo_new_from_compose(compose);
1095 /* decode \-escape sequences in the internal representation of the quote format */
1096 tmp = malloc(strlen(subject_format)+1);
1097 pref_get_unescaped_pref(tmp, subject_format);
1099 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1101 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1102 compose->gtkaspell);
1104 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1106 quote_fmt_scan_string(tmp);
1109 buf = quote_fmt_get_buffer();
1111 alertpanel_error(_("New message subject format error."));
1113 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1114 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1115 quote_fmt_reset_vartable();
1119 mfield = SUBJECT_FIELD_PRESENT;
1123 && *body_format != '\0' )
1126 GtkTextBuffer *buffer;
1127 GtkTextIter start, end;
1131 dummyinfo = compose_msginfo_new_from_compose(compose);
1133 text = GTK_TEXT_VIEW(compose->text);
1134 buffer = gtk_text_view_get_buffer(text);
1135 gtk_text_buffer_get_start_iter(buffer, &start);
1136 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1137 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1139 compose_quote_fmt(compose, dummyinfo,
1141 NULL, tmp, FALSE, TRUE,
1142 _("The body of the \"New message\" template has an error at line %d."));
1143 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1144 quote_fmt_reset_vartable();
1148 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1149 gtkaspell_highlight_all(compose->gtkaspell);
1151 mfield = BODY_FIELD_PRESENT;
1155 procmsg_msginfo_free( dummyinfo );
1161 for (i = 0; i < attach_files->len; i++) {
1162 file = g_ptr_array_index(attach_files, i);
1163 compose_attach_append(compose, file, file, NULL);
1167 compose_show_first_last_header(compose, TRUE);
1169 /* Set save folder */
1170 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1171 gchar *folderidentifier;
1173 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1174 folderidentifier = folder_item_get_identifier(item);
1175 compose_set_save_to(compose, folderidentifier);
1176 g_free(folderidentifier);
1179 /* Place cursor according to provided input (mfield) */
1181 case NO_FIELD_PRESENT:
1182 if (compose->header_last)
1183 gtk_widget_grab_focus(compose->header_last->entry);
1185 case TO_FIELD_PRESENT:
1186 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1188 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1191 gtk_widget_grab_focus(compose->subject_entry);
1193 case SUBJECT_FIELD_PRESENT:
1194 textview = GTK_TEXT_VIEW(compose->text);
1197 textbuf = gtk_text_view_get_buffer(textview);
1200 mark = gtk_text_buffer_get_insert(textbuf);
1201 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1202 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1204 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1205 * only defers where it comes to the variable body
1206 * is not null. If no body is present compose->text
1207 * will be null in which case you cannot place the
1208 * cursor inside the component so. An empty component
1209 * is therefore created before placing the cursor
1211 case BODY_FIELD_PRESENT:
1212 gtk_widget_grab_focus(compose->text);
1216 undo_unblock(compose->undostruct);
1218 if (prefs_common.auto_exteditor)
1219 compose_exec_ext_editor(compose);
1221 compose->draft_timeout_tag = -1;
1222 SCROLL_TO_CURSOR(compose);
1224 compose->modified = FALSE;
1225 compose_set_title(compose);
1227 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1232 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1233 gboolean override_pref, const gchar *system)
1235 const gchar *privacy = NULL;
1237 cm_return_if_fail(compose != NULL);
1238 cm_return_if_fail(account != NULL);
1240 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1245 else if (account->default_privacy_system
1246 && strlen(account->default_privacy_system)) {
1247 privacy = account->default_privacy_system;
1249 GSList *privacy_avail = privacy_get_system_ids();
1250 if (privacy_avail && g_slist_length(privacy_avail)) {
1251 privacy = (gchar *)(privacy_avail->data);
1254 if (privacy != NULL) {
1256 g_free(compose->privacy_system);
1257 compose->privacy_system = NULL;
1259 if (compose->privacy_system == NULL)
1260 compose->privacy_system = g_strdup(privacy);
1261 else if (*(compose->privacy_system) == '\0') {
1262 g_free(compose->privacy_system);
1263 compose->privacy_system = g_strdup(privacy);
1265 compose_update_privacy_system_menu_item(compose, FALSE);
1266 compose_use_encryption(compose, TRUE);
1270 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1272 const gchar *privacy = NULL;
1276 else if (account->default_privacy_system
1277 && strlen(account->default_privacy_system)) {
1278 privacy = account->default_privacy_system;
1280 GSList *privacy_avail = privacy_get_system_ids();
1281 if (privacy_avail && g_slist_length(privacy_avail)) {
1282 privacy = (gchar *)(privacy_avail->data);
1286 if (privacy != NULL) {
1288 g_free(compose->privacy_system);
1289 compose->privacy_system = NULL;
1291 if (compose->privacy_system == NULL)
1292 compose->privacy_system = g_strdup(privacy);
1293 compose_update_privacy_system_menu_item(compose, FALSE);
1294 compose_use_signing(compose, TRUE);
1298 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1302 Compose *compose = NULL;
1304 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1306 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1307 cm_return_val_if_fail(msginfo != NULL, NULL);
1309 list_len = g_slist_length(msginfo_list);
1313 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1314 FALSE, prefs_common.default_reply_list, FALSE, body);
1316 case COMPOSE_REPLY_WITH_QUOTE:
1317 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1318 FALSE, prefs_common.default_reply_list, FALSE, body);
1320 case COMPOSE_REPLY_WITHOUT_QUOTE:
1321 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1322 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1324 case COMPOSE_REPLY_TO_SENDER:
1325 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1326 FALSE, FALSE, TRUE, body);
1328 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1329 compose = compose_followup_and_reply_to(msginfo,
1330 COMPOSE_QUOTE_CHECK,
1331 FALSE, FALSE, body);
1333 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1334 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1335 FALSE, FALSE, TRUE, body);
1337 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1338 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1339 FALSE, FALSE, TRUE, NULL);
1341 case COMPOSE_REPLY_TO_ALL:
1342 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1343 TRUE, FALSE, FALSE, body);
1345 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1346 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1347 TRUE, FALSE, FALSE, body);
1349 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1350 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1351 TRUE, FALSE, FALSE, NULL);
1353 case COMPOSE_REPLY_TO_LIST:
1354 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1355 FALSE, TRUE, FALSE, body);
1357 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1358 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1359 FALSE, TRUE, FALSE, body);
1361 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1362 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1363 FALSE, TRUE, FALSE, NULL);
1365 case COMPOSE_FORWARD:
1366 if (prefs_common.forward_as_attachment) {
1367 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1370 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1374 case COMPOSE_FORWARD_INLINE:
1375 /* check if we reply to more than one Message */
1376 if (list_len == 1) {
1377 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1380 /* more messages FALL THROUGH */
1381 case COMPOSE_FORWARD_AS_ATTACH:
1382 compose = compose_forward_multiple(NULL, msginfo_list);
1384 case COMPOSE_REDIRECT:
1385 compose = compose_redirect(NULL, msginfo, FALSE);
1388 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1391 if (compose == NULL) {
1392 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1396 compose->rmode = mode;
1397 switch (compose->rmode) {
1399 case COMPOSE_REPLY_WITH_QUOTE:
1400 case COMPOSE_REPLY_WITHOUT_QUOTE:
1401 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1402 debug_print("reply mode Normal\n");
1403 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1404 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1406 case COMPOSE_REPLY_TO_SENDER:
1407 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1408 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1409 debug_print("reply mode Sender\n");
1410 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1412 case COMPOSE_REPLY_TO_ALL:
1413 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1414 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1415 debug_print("reply mode All\n");
1416 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1418 case COMPOSE_REPLY_TO_LIST:
1419 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1420 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1421 debug_print("reply mode List\n");
1422 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1430 static Compose *compose_reply(MsgInfo *msginfo,
1431 ComposeQuoteMode quote_mode,
1437 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1438 to_sender, FALSE, body);
1441 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1442 ComposeQuoteMode quote_mode,
1447 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1448 to_sender, TRUE, body);
1451 static void compose_extract_original_charset(Compose *compose)
1453 MsgInfo *info = NULL;
1454 if (compose->replyinfo) {
1455 info = compose->replyinfo;
1456 } else if (compose->fwdinfo) {
1457 info = compose->fwdinfo;
1458 } else if (compose->targetinfo) {
1459 info = compose->targetinfo;
1462 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1463 MimeInfo *partinfo = mimeinfo;
1464 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1465 partinfo = procmime_mimeinfo_next(partinfo);
1467 compose->orig_charset =
1468 g_strdup(procmime_mimeinfo_get_parameter(
1469 partinfo, "charset"));
1471 procmime_mimeinfo_free_all(mimeinfo);
1475 #define SIGNAL_BLOCK(buffer) { \
1476 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1477 G_CALLBACK(compose_changed_cb), \
1479 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1480 G_CALLBACK(text_inserted), \
1484 #define SIGNAL_UNBLOCK(buffer) { \
1485 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1486 G_CALLBACK(compose_changed_cb), \
1488 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1489 G_CALLBACK(text_inserted), \
1493 static Compose *compose_generic_reply(MsgInfo *msginfo,
1494 ComposeQuoteMode quote_mode,
1495 gboolean to_all, gboolean to_ml,
1497 gboolean followup_and_reply_to,
1501 PrefsAccount *account = NULL;
1502 GtkTextView *textview;
1503 GtkTextBuffer *textbuf;
1504 gboolean quote = FALSE;
1505 const gchar *qmark = NULL;
1506 const gchar *body_fmt = NULL;
1507 gchar *s_system = NULL;
1509 cm_return_val_if_fail(msginfo != NULL, NULL);
1510 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1512 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1514 cm_return_val_if_fail(account != NULL, NULL);
1516 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1518 compose->updating = TRUE;
1520 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1521 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1523 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1524 if (!compose->replyinfo)
1525 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1527 compose_extract_original_charset(compose);
1529 if (msginfo->folder && msginfo->folder->ret_rcpt)
1530 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1532 /* Set save folder */
1533 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1534 gchar *folderidentifier;
1536 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1537 folderidentifier = folder_item_get_identifier(msginfo->folder);
1538 compose_set_save_to(compose, folderidentifier);
1539 g_free(folderidentifier);
1542 if (compose_parse_header(compose, msginfo) < 0) {
1543 compose->updating = FALSE;
1544 compose_destroy(compose);
1548 /* override from name according to folder properties */
1549 if (msginfo->folder && msginfo->folder->prefs &&
1550 msginfo->folder->prefs->reply_with_format &&
1551 msginfo->folder->prefs->reply_override_from_format &&
1552 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1557 /* decode \-escape sequences in the internal representation of the quote format */
1558 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1559 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1562 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1563 compose->gtkaspell);
1565 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1567 quote_fmt_scan_string(tmp);
1570 buf = quote_fmt_get_buffer();
1572 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1574 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1575 quote_fmt_reset_vartable();
1580 textview = (GTK_TEXT_VIEW(compose->text));
1581 textbuf = gtk_text_view_get_buffer(textview);
1582 compose_create_tags(textview, compose);
1584 undo_block(compose->undostruct);
1586 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1589 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1590 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1591 /* use the reply format of folder (if enabled), or the account's one
1592 (if enabled) or fallback to the global reply format, which is always
1593 enabled (even if empty), and use the relevant quotemark */
1595 if (msginfo->folder && msginfo->folder->prefs &&
1596 msginfo->folder->prefs->reply_with_format) {
1597 qmark = msginfo->folder->prefs->reply_quotemark;
1598 body_fmt = msginfo->folder->prefs->reply_body_format;
1600 } else if (account->reply_with_format) {
1601 qmark = account->reply_quotemark;
1602 body_fmt = account->reply_body_format;
1605 qmark = prefs_common.quotemark;
1606 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1607 body_fmt = gettext(prefs_common.quotefmt);
1614 /* empty quotemark is not allowed */
1615 if (qmark == NULL || *qmark == '\0')
1617 compose_quote_fmt(compose, compose->replyinfo,
1618 body_fmt, qmark, body, FALSE, TRUE,
1619 _("The body of the \"Reply\" template has an error at line %d."));
1620 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1621 quote_fmt_reset_vartable();
1623 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1624 gtkaspell_highlight_all(compose->gtkaspell);
1628 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1629 compose_force_encryption(compose, account, FALSE, s_system);
1632 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1633 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1634 compose_force_signing(compose, account, s_system);
1638 SIGNAL_BLOCK(textbuf);
1640 if (account->auto_sig)
1641 compose_insert_sig(compose, FALSE);
1643 compose_wrap_all(compose);
1645 SIGNAL_UNBLOCK(textbuf);
1647 gtk_widget_grab_focus(compose->text);
1649 undo_unblock(compose->undostruct);
1651 if (prefs_common.auto_exteditor)
1652 compose_exec_ext_editor(compose);
1654 compose->modified = FALSE;
1655 compose_set_title(compose);
1657 compose->updating = FALSE;
1658 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1659 SCROLL_TO_CURSOR(compose);
1661 if (compose->deferred_destroy) {
1662 compose_destroy(compose);
1670 #define INSERT_FW_HEADER(var, hdr) \
1671 if (msginfo->var && *msginfo->var) { \
1672 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1673 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1674 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1677 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1678 gboolean as_attach, const gchar *body,
1679 gboolean no_extedit,
1683 GtkTextView *textview;
1684 GtkTextBuffer *textbuf;
1687 cm_return_val_if_fail(msginfo != NULL, NULL);
1688 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1691 !(account = compose_guess_forward_account_from_msginfo
1693 account = cur_account;
1695 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1697 compose->updating = TRUE;
1698 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1699 if (!compose->fwdinfo)
1700 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1702 compose_extract_original_charset(compose);
1704 if (msginfo->subject && *msginfo->subject) {
1705 gchar *buf, *buf2, *p;
1707 buf = p = g_strdup(msginfo->subject);
1708 p += subject_get_prefix_length(p);
1709 memmove(buf, p, strlen(p) + 1);
1711 buf2 = g_strdup_printf("Fw: %s", buf);
1712 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1718 /* override from name according to folder properties */
1719 if (msginfo->folder && msginfo->folder->prefs &&
1720 msginfo->folder->prefs->forward_with_format &&
1721 msginfo->folder->prefs->forward_override_from_format &&
1722 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1726 MsgInfo *full_msginfo = NULL;
1729 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1731 full_msginfo = procmsg_msginfo_copy(msginfo);
1733 /* decode \-escape sequences in the internal representation of the quote format */
1734 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1735 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1738 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1739 compose->gtkaspell);
1741 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1743 quote_fmt_scan_string(tmp);
1746 buf = quote_fmt_get_buffer();
1748 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1750 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1751 quote_fmt_reset_vartable();
1754 procmsg_msginfo_free(full_msginfo);
1757 textview = GTK_TEXT_VIEW(compose->text);
1758 textbuf = gtk_text_view_get_buffer(textview);
1759 compose_create_tags(textview, compose);
1761 undo_block(compose->undostruct);
1765 msgfile = procmsg_get_message_file(msginfo);
1766 if (!is_file_exist(msgfile))
1767 g_warning("%s: file not exist\n", msgfile);
1769 compose_attach_append(compose, msgfile, msgfile,
1774 const gchar *qmark = NULL;
1775 const gchar *body_fmt = NULL;
1776 MsgInfo *full_msginfo;
1778 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1779 body_fmt = gettext(prefs_common.fw_quotefmt);
1783 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1785 full_msginfo = procmsg_msginfo_copy(msginfo);
1787 /* use the forward format of folder (if enabled), or the account's one
1788 (if enabled) or fallback to the global forward format, which is always
1789 enabled (even if empty), and use the relevant quotemark */
1790 if (msginfo->folder && msginfo->folder->prefs &&
1791 msginfo->folder->prefs->forward_with_format) {
1792 qmark = msginfo->folder->prefs->forward_quotemark;
1793 body_fmt = msginfo->folder->prefs->forward_body_format;
1795 } else if (account->forward_with_format) {
1796 qmark = account->forward_quotemark;
1797 body_fmt = account->forward_body_format;
1800 qmark = prefs_common.fw_quotemark;
1801 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1802 body_fmt = gettext(prefs_common.fw_quotefmt);
1807 /* empty quotemark is not allowed */
1808 if (qmark == NULL || *qmark == '\0')
1811 compose_quote_fmt(compose, full_msginfo,
1812 body_fmt, qmark, body, FALSE, TRUE,
1813 _("The body of the \"Forward\" template has an error at line %d."));
1814 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1815 quote_fmt_reset_vartable();
1816 compose_attach_parts(compose, msginfo);
1818 procmsg_msginfo_free(full_msginfo);
1820 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1821 gtkaspell_highlight_all(compose->gtkaspell);
1825 SIGNAL_BLOCK(textbuf);
1827 if (account->auto_sig)
1828 compose_insert_sig(compose, FALSE);
1830 compose_wrap_all(compose);
1832 SIGNAL_UNBLOCK(textbuf);
1834 gtk_text_buffer_get_start_iter(textbuf, &iter);
1835 gtk_text_buffer_place_cursor(textbuf, &iter);
1837 gtk_widget_grab_focus(compose->header_last->entry);
1839 if (!no_extedit && prefs_common.auto_exteditor)
1840 compose_exec_ext_editor(compose);
1843 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1844 gchar *folderidentifier;
1846 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1847 folderidentifier = folder_item_get_identifier(msginfo->folder);
1848 compose_set_save_to(compose, folderidentifier);
1849 g_free(folderidentifier);
1852 undo_unblock(compose->undostruct);
1854 compose->modified = FALSE;
1855 compose_set_title(compose);
1857 compose->updating = FALSE;
1858 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1859 SCROLL_TO_CURSOR(compose);
1861 if (compose->deferred_destroy) {
1862 compose_destroy(compose);
1866 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1871 #undef INSERT_FW_HEADER
1873 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1876 GtkTextView *textview;
1877 GtkTextBuffer *textbuf;
1881 gboolean single_mail = TRUE;
1883 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1885 if (g_slist_length(msginfo_list) > 1)
1886 single_mail = FALSE;
1888 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1889 if (((MsgInfo *)msginfo->data)->folder == NULL)
1892 /* guess account from first selected message */
1894 !(account = compose_guess_forward_account_from_msginfo
1895 (msginfo_list->data)))
1896 account = cur_account;
1898 cm_return_val_if_fail(account != NULL, NULL);
1900 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1901 if (msginfo->data) {
1902 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1903 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1907 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1908 g_warning("no msginfo_list");
1912 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1914 compose->updating = TRUE;
1916 /* override from name according to folder properties */
1917 if (msginfo_list->data) {
1918 MsgInfo *msginfo = msginfo_list->data;
1920 if (msginfo->folder && msginfo->folder->prefs &&
1921 msginfo->folder->prefs->forward_with_format &&
1922 msginfo->folder->prefs->forward_override_from_format &&
1923 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1928 /* decode \-escape sequences in the internal representation of the quote format */
1929 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1930 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1933 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1934 compose->gtkaspell);
1936 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1938 quote_fmt_scan_string(tmp);
1941 buf = quote_fmt_get_buffer();
1943 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1945 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1946 quote_fmt_reset_vartable();
1952 textview = GTK_TEXT_VIEW(compose->text);
1953 textbuf = gtk_text_view_get_buffer(textview);
1954 compose_create_tags(textview, compose);
1956 undo_block(compose->undostruct);
1957 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1958 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1960 if (!is_file_exist(msgfile))
1961 g_warning("%s: file not exist\n", msgfile);
1963 compose_attach_append(compose, msgfile, msgfile,
1969 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1970 if (info->subject && *info->subject) {
1971 gchar *buf, *buf2, *p;
1973 buf = p = g_strdup(info->subject);
1974 p += subject_get_prefix_length(p);
1975 memmove(buf, p, strlen(p) + 1);
1977 buf2 = g_strdup_printf("Fw: %s", buf);
1978 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1984 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1985 _("Fw: multiple emails"));
1988 SIGNAL_BLOCK(textbuf);
1990 if (account->auto_sig)
1991 compose_insert_sig(compose, FALSE);
1993 compose_wrap_all(compose);
1995 SIGNAL_UNBLOCK(textbuf);
1997 gtk_text_buffer_get_start_iter(textbuf, &iter);
1998 gtk_text_buffer_place_cursor(textbuf, &iter);
2000 gtk_widget_grab_focus(compose->header_last->entry);
2001 undo_unblock(compose->undostruct);
2002 compose->modified = FALSE;
2003 compose_set_title(compose);
2005 compose->updating = FALSE;
2006 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2007 SCROLL_TO_CURSOR(compose);
2009 if (compose->deferred_destroy) {
2010 compose_destroy(compose);
2014 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2019 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2021 GtkTextIter start = *iter;
2022 GtkTextIter end_iter;
2023 int start_pos = gtk_text_iter_get_offset(&start);
2025 if (!compose->account->sig_sep)
2028 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2029 start_pos+strlen(compose->account->sig_sep));
2031 /* check sig separator */
2032 str = gtk_text_iter_get_text(&start, &end_iter);
2033 if (!strcmp(str, compose->account->sig_sep)) {
2035 /* check end of line (\n) */
2036 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2037 start_pos+strlen(compose->account->sig_sep));
2038 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2039 start_pos+strlen(compose->account->sig_sep)+1);
2040 tmp = gtk_text_iter_get_text(&start, &end_iter);
2041 if (!strcmp(tmp,"\n")) {
2053 static void compose_colorize_signature(Compose *compose)
2055 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2057 GtkTextIter end_iter;
2058 gtk_text_buffer_get_start_iter(buffer, &iter);
2059 while (gtk_text_iter_forward_line(&iter))
2060 if (compose_is_sig_separator(compose, buffer, &iter)) {
2061 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2062 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2066 #define BLOCK_WRAP() { \
2067 prev_autowrap = compose->autowrap; \
2068 buffer = gtk_text_view_get_buffer( \
2069 GTK_TEXT_VIEW(compose->text)); \
2070 compose->autowrap = FALSE; \
2072 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2073 G_CALLBACK(compose_changed_cb), \
2075 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2076 G_CALLBACK(text_inserted), \
2079 #define UNBLOCK_WRAP() { \
2080 compose->autowrap = prev_autowrap; \
2081 if (compose->autowrap) { \
2082 gint old = compose->draft_timeout_tag; \
2083 compose->draft_timeout_tag = -2; \
2084 compose_wrap_all(compose); \
2085 compose->draft_timeout_tag = old; \
2088 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2089 G_CALLBACK(compose_changed_cb), \
2091 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2092 G_CALLBACK(text_inserted), \
2096 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2098 Compose *compose = NULL;
2099 PrefsAccount *account = NULL;
2100 GtkTextView *textview;
2101 GtkTextBuffer *textbuf;
2105 gchar buf[BUFFSIZE];
2106 gboolean use_signing = FALSE;
2107 gboolean use_encryption = FALSE;
2108 gchar *privacy_system = NULL;
2109 int priority = PRIORITY_NORMAL;
2110 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2111 gboolean autowrap = prefs_common.autowrap;
2112 gboolean autoindent = prefs_common.auto_indent;
2114 cm_return_val_if_fail(msginfo != NULL, NULL);
2115 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2117 if (compose_put_existing_to_front(msginfo)) {
2121 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2122 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2123 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2124 gchar queueheader_buf[BUFFSIZE];
2127 /* Select Account from queue headers */
2128 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2129 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2130 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2131 account = account_find_from_id(id);
2133 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2134 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2135 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2136 account = account_find_from_id(id);
2138 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2139 sizeof(queueheader_buf), "NAID:")) {
2140 id = atoi(&queueheader_buf[strlen("NAID:")]);
2141 account = account_find_from_id(id);
2143 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2144 sizeof(queueheader_buf), "MAID:")) {
2145 id = atoi(&queueheader_buf[strlen("MAID:")]);
2146 account = account_find_from_id(id);
2148 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2149 sizeof(queueheader_buf), "S:")) {
2150 account = account_find_from_address(queueheader_buf, FALSE);
2152 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2153 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2154 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2155 use_signing = param;
2158 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2159 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2160 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2161 use_signing = param;
2164 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2165 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2166 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2167 use_encryption = param;
2169 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2170 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2171 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2172 use_encryption = param;
2174 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2175 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2176 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2179 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2180 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2181 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2184 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2185 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2186 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2188 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2189 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2190 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2192 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2193 sizeof(queueheader_buf), "X-Priority: ")) {
2194 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2197 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2198 sizeof(queueheader_buf), "RMID:")) {
2199 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2200 if (tokens[0] && tokens[1] && tokens[2]) {
2201 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2202 if (orig_item != NULL) {
2203 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2208 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2209 sizeof(queueheader_buf), "FMID:")) {
2210 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2211 if (tokens[0] && tokens[1] && tokens[2]) {
2212 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2213 if (orig_item != NULL) {
2214 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2220 account = msginfo->folder->folder->account;
2223 if (!account && prefs_common.reedit_account_autosel) {
2224 gchar from[BUFFSIZE];
2225 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2226 extract_address(from);
2227 account = account_find_from_address(from, FALSE);
2231 account = cur_account;
2233 cm_return_val_if_fail(account != NULL, NULL);
2235 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2237 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2238 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2239 compose->autowrap = autowrap;
2240 compose->replyinfo = replyinfo;
2241 compose->fwdinfo = fwdinfo;
2243 compose->updating = TRUE;
2244 compose->priority = priority;
2246 if (privacy_system != NULL) {
2247 compose->privacy_system = privacy_system;
2248 compose_use_signing(compose, use_signing);
2249 compose_use_encryption(compose, use_encryption);
2250 compose_update_privacy_system_menu_item(compose, FALSE);
2252 activate_privacy_system(compose, account, FALSE);
2255 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2257 compose_extract_original_charset(compose);
2259 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2260 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2261 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2262 gchar queueheader_buf[BUFFSIZE];
2264 /* Set message save folder */
2265 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2266 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2267 compose_set_save_to(compose, &queueheader_buf[4]);
2269 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2270 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2272 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2277 if (compose_parse_header(compose, msginfo) < 0) {
2278 compose->updating = FALSE;
2279 compose_destroy(compose);
2282 compose_reedit_set_entry(compose, msginfo);
2284 textview = GTK_TEXT_VIEW(compose->text);
2285 textbuf = gtk_text_view_get_buffer(textview);
2286 compose_create_tags(textview, compose);
2288 mark = gtk_text_buffer_get_insert(textbuf);
2289 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2291 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2292 G_CALLBACK(compose_changed_cb),
2295 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2296 fp = procmime_get_first_encrypted_text_content(msginfo);
2298 compose_force_encryption(compose, account, TRUE, NULL);
2301 fp = procmime_get_first_text_content(msginfo);
2304 g_warning("Can't get text part\n");
2308 gboolean prev_autowrap = compose->autowrap;
2309 GtkTextBuffer *buffer = textbuf;
2311 while (fgets(buf, sizeof(buf), fp) != NULL) {
2313 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2319 compose_attach_parts(compose, msginfo);
2321 compose_colorize_signature(compose);
2323 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2324 G_CALLBACK(compose_changed_cb),
2327 gtk_widget_grab_focus(compose->text);
2329 if (prefs_common.auto_exteditor) {
2330 compose_exec_ext_editor(compose);
2332 compose->modified = FALSE;
2333 compose_set_title(compose);
2335 compose->updating = FALSE;
2336 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2337 SCROLL_TO_CURSOR(compose);
2339 if (compose->deferred_destroy) {
2340 compose_destroy(compose);
2344 compose->sig_str = account_get_signature_str(compose->account);
2346 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2351 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2358 cm_return_val_if_fail(msginfo != NULL, NULL);
2361 account = account_get_reply_account(msginfo,
2362 prefs_common.reply_account_autosel);
2363 cm_return_val_if_fail(account != NULL, NULL);
2365 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2367 compose->updating = TRUE;
2369 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2370 compose->replyinfo = NULL;
2371 compose->fwdinfo = NULL;
2373 compose_show_first_last_header(compose, TRUE);
2375 gtk_widget_grab_focus(compose->header_last->entry);
2377 filename = procmsg_get_message_file(msginfo);
2379 if (filename == NULL) {
2380 compose->updating = FALSE;
2381 compose_destroy(compose);
2386 compose->redirect_filename = filename;
2388 /* Set save folder */
2389 item = msginfo->folder;
2390 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2391 gchar *folderidentifier;
2393 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2394 folderidentifier = folder_item_get_identifier(item);
2395 compose_set_save_to(compose, folderidentifier);
2396 g_free(folderidentifier);
2399 compose_attach_parts(compose, msginfo);
2401 if (msginfo->subject)
2402 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2404 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2406 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2407 _("The body of the \"Redirect\" template has an error at line %d."));
2408 quote_fmt_reset_vartable();
2409 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2411 compose_colorize_signature(compose);
2414 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2415 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2416 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2418 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2419 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2420 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2421 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2422 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2423 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2424 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2425 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2427 if (compose->toolbar->draft_btn)
2428 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2429 if (compose->toolbar->insert_btn)
2430 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2431 if (compose->toolbar->attach_btn)
2432 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2433 if (compose->toolbar->sig_btn)
2434 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2435 if (compose->toolbar->exteditor_btn)
2436 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2437 if (compose->toolbar->linewrap_current_btn)
2438 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2439 if (compose->toolbar->linewrap_all_btn)
2440 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2442 compose->modified = FALSE;
2443 compose_set_title(compose);
2444 compose->updating = FALSE;
2445 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2446 SCROLL_TO_CURSOR(compose);
2448 if (compose->deferred_destroy) {
2449 compose_destroy(compose);
2453 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2458 GList *compose_get_compose_list(void)
2460 return compose_list;
2463 void compose_entry_append(Compose *compose, const gchar *address,
2464 ComposeEntryType type, ComposePrefType pref_type)
2466 const gchar *header;
2468 gboolean in_quote = FALSE;
2469 if (!address || *address == '\0') return;
2476 header = N_("Bcc:");
2478 case COMPOSE_REPLYTO:
2479 header = N_("Reply-To:");
2481 case COMPOSE_NEWSGROUPS:
2482 header = N_("Newsgroups:");
2484 case COMPOSE_FOLLOWUPTO:
2485 header = N_( "Followup-To:");
2487 case COMPOSE_INREPLYTO:
2488 header = N_( "In-Reply-To:");
2495 header = prefs_common_translated_header_name(header);
2497 cur = begin = (gchar *)address;
2499 /* we separate the line by commas, but not if we're inside a quoted
2501 while (*cur != '\0') {
2503 in_quote = !in_quote;
2504 if (*cur == ',' && !in_quote) {
2505 gchar *tmp = g_strdup(begin);
2507 tmp[cur-begin]='\0';
2510 while (*tmp == ' ' || *tmp == '\t')
2512 compose_add_header_entry(compose, header, tmp, pref_type);
2519 gchar *tmp = g_strdup(begin);
2521 tmp[cur-begin]='\0';
2524 while (*tmp == ' ' || *tmp == '\t')
2526 compose_add_header_entry(compose, header, tmp, pref_type);
2531 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2533 static GdkColor yellow;
2534 static GdkColor black;
2535 static gboolean yellow_initialised = FALSE;
2539 if (!yellow_initialised) {
2540 gdk_color_parse("#f5f6be", &yellow);
2541 gdk_color_parse("#000000", &black);
2542 yellow_initialised = gdk_colormap_alloc_color(
2543 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2544 yellow_initialised &= gdk_colormap_alloc_color(
2545 gdk_colormap_get_system(), &black, FALSE, TRUE);
2548 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2549 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2550 if (gtk_entry_get_text(entry) &&
2551 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2552 if (yellow_initialised) {
2553 gtk_widget_modify_base(
2554 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2555 GTK_STATE_NORMAL, &yellow);
2556 gtk_widget_modify_text(
2557 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2558 GTK_STATE_NORMAL, &black);
2564 void compose_toolbar_cb(gint action, gpointer data)
2566 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2567 Compose *compose = (Compose*)toolbar_item->parent;
2569 cm_return_if_fail(compose != NULL);
2573 compose_send_cb(NULL, compose);
2576 compose_send_later_cb(NULL, compose);
2579 compose_draft(compose, COMPOSE_QUIT_EDITING);
2582 compose_insert_file_cb(NULL, compose);
2585 compose_attach_cb(NULL, compose);
2588 compose_insert_sig(compose, FALSE);
2591 compose_ext_editor_cb(NULL, compose);
2593 case A_LINEWRAP_CURRENT:
2594 compose_beautify_paragraph(compose, NULL, TRUE);
2596 case A_LINEWRAP_ALL:
2597 compose_wrap_all_full(compose, TRUE);
2600 compose_address_cb(NULL, compose);
2603 case A_CHECK_SPELLING:
2604 compose_check_all(NULL, compose);
2612 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2617 gchar *subject = NULL;
2621 gchar **attach = NULL;
2622 gchar *inreplyto = NULL;
2623 MailField mfield = NO_FIELD_PRESENT;
2625 /* get mailto parts but skip from */
2626 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2629 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2630 mfield = TO_FIELD_PRESENT;
2633 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2635 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2637 if (!g_utf8_validate (subject, -1, NULL)) {
2638 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2639 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2642 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2644 mfield = SUBJECT_FIELD_PRESENT;
2647 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2648 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2651 gboolean prev_autowrap = compose->autowrap;
2653 compose->autowrap = FALSE;
2655 mark = gtk_text_buffer_get_insert(buffer);
2656 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2658 if (!g_utf8_validate (body, -1, NULL)) {
2659 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2660 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2663 gtk_text_buffer_insert(buffer, &iter, body, -1);
2665 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2667 compose->autowrap = prev_autowrap;
2668 if (compose->autowrap)
2669 compose_wrap_all(compose);
2670 mfield = BODY_FIELD_PRESENT;
2674 gint i = 0, att = 0;
2675 gchar *warn_files = NULL;
2676 while (attach[i] != NULL) {
2677 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2678 if (utf8_filename) {
2679 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2680 gchar *tmp = g_strdup_printf("%s%s\n",
2681 warn_files?warn_files:"",
2687 g_free(utf8_filename);
2689 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2694 alertpanel_notice(ngettext(
2695 "The following file has been attached: \n%s",
2696 "The following files have been attached: \n%s", att), warn_files);
2701 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2714 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2716 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2717 {"Cc:", NULL, TRUE},
2718 {"References:", NULL, FALSE},
2719 {"Bcc:", NULL, TRUE},
2720 {"Newsgroups:", NULL, TRUE},
2721 {"Followup-To:", NULL, TRUE},
2722 {"List-Post:", NULL, FALSE},
2723 {"X-Priority:", NULL, FALSE},
2724 {NULL, NULL, FALSE}};
2740 cm_return_val_if_fail(msginfo != NULL, -1);
2742 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2743 procheader_get_header_fields(fp, hentry);
2746 if (hentry[H_REPLY_TO].body != NULL) {
2747 if (hentry[H_REPLY_TO].body[0] != '\0') {
2749 conv_unmime_header(hentry[H_REPLY_TO].body,
2752 g_free(hentry[H_REPLY_TO].body);
2753 hentry[H_REPLY_TO].body = NULL;
2755 if (hentry[H_CC].body != NULL) {
2756 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2757 g_free(hentry[H_CC].body);
2758 hentry[H_CC].body = NULL;
2760 if (hentry[H_REFERENCES].body != NULL) {
2761 if (compose->mode == COMPOSE_REEDIT)
2762 compose->references = hentry[H_REFERENCES].body;
2764 compose->references = compose_parse_references
2765 (hentry[H_REFERENCES].body, msginfo->msgid);
2766 g_free(hentry[H_REFERENCES].body);
2768 hentry[H_REFERENCES].body = NULL;
2770 if (hentry[H_BCC].body != NULL) {
2771 if (compose->mode == COMPOSE_REEDIT)
2773 conv_unmime_header(hentry[H_BCC].body, NULL);
2774 g_free(hentry[H_BCC].body);
2775 hentry[H_BCC].body = NULL;
2777 if (hentry[H_NEWSGROUPS].body != NULL) {
2778 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2779 hentry[H_NEWSGROUPS].body = NULL;
2781 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2782 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2783 compose->followup_to =
2784 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2787 g_free(hentry[H_FOLLOWUP_TO].body);
2788 hentry[H_FOLLOWUP_TO].body = NULL;
2790 if (hentry[H_LIST_POST].body != NULL) {
2791 gchar *to = NULL, *start = NULL;
2793 extract_address(hentry[H_LIST_POST].body);
2794 if (hentry[H_LIST_POST].body[0] != '\0') {
2795 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2797 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2798 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2801 g_free(compose->ml_post);
2802 compose->ml_post = to;
2805 g_free(hentry[H_LIST_POST].body);
2806 hentry[H_LIST_POST].body = NULL;
2809 /* CLAWS - X-Priority */
2810 if (compose->mode == COMPOSE_REEDIT)
2811 if (hentry[H_X_PRIORITY].body != NULL) {
2814 priority = atoi(hentry[H_X_PRIORITY].body);
2815 g_free(hentry[H_X_PRIORITY].body);
2817 hentry[H_X_PRIORITY].body = NULL;
2819 if (priority < PRIORITY_HIGHEST ||
2820 priority > PRIORITY_LOWEST)
2821 priority = PRIORITY_NORMAL;
2823 compose->priority = priority;
2826 if (compose->mode == COMPOSE_REEDIT) {
2827 if (msginfo->inreplyto && *msginfo->inreplyto)
2828 compose->inreplyto = g_strdup(msginfo->inreplyto);
2832 if (msginfo->msgid && *msginfo->msgid)
2833 compose->inreplyto = g_strdup(msginfo->msgid);
2835 if (!compose->references) {
2836 if (msginfo->msgid && *msginfo->msgid) {
2837 if (msginfo->inreplyto && *msginfo->inreplyto)
2838 compose->references =
2839 g_strdup_printf("<%s>\n\t<%s>",
2843 compose->references =
2844 g_strconcat("<", msginfo->msgid, ">",
2846 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2847 compose->references =
2848 g_strconcat("<", msginfo->inreplyto, ">",
2856 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2858 GSList *ref_id_list, *cur;
2862 ref_id_list = references_list_append(NULL, ref);
2863 if (!ref_id_list) return NULL;
2864 if (msgid && *msgid)
2865 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2870 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2871 /* "<" + Message-ID + ">" + CR+LF+TAB */
2872 len += strlen((gchar *)cur->data) + 5;
2874 if (len > MAX_REFERENCES_LEN) {
2875 /* remove second message-ID */
2876 if (ref_id_list && ref_id_list->next &&
2877 ref_id_list->next->next) {
2878 g_free(ref_id_list->next->data);
2879 ref_id_list = g_slist_remove
2880 (ref_id_list, ref_id_list->next->data);
2882 slist_free_strings(ref_id_list);
2883 g_slist_free(ref_id_list);
2890 new_ref = g_string_new("");
2891 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2892 if (new_ref->len > 0)
2893 g_string_append(new_ref, "\n\t");
2894 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2897 slist_free_strings(ref_id_list);
2898 g_slist_free(ref_id_list);
2900 new_ref_str = new_ref->str;
2901 g_string_free(new_ref, FALSE);
2906 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2907 const gchar *fmt, const gchar *qmark,
2908 const gchar *body, gboolean rewrap,
2909 gboolean need_unescape,
2910 const gchar *err_msg)
2912 MsgInfo* dummyinfo = NULL;
2913 gchar *quote_str = NULL;
2915 gboolean prev_autowrap;
2916 const gchar *trimmed_body = body;
2917 gint cursor_pos = -1;
2918 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2919 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2924 SIGNAL_BLOCK(buffer);
2927 dummyinfo = compose_msginfo_new_from_compose(compose);
2928 msginfo = dummyinfo;
2931 if (qmark != NULL) {
2933 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2934 compose->gtkaspell);
2936 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2938 quote_fmt_scan_string(qmark);
2941 buf = quote_fmt_get_buffer();
2943 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2945 Xstrdup_a(quote_str, buf, goto error)
2948 if (fmt && *fmt != '\0') {
2951 while (*trimmed_body == '\n')
2955 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2956 compose->gtkaspell);
2958 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2960 if (need_unescape) {
2963 /* decode \-escape sequences in the internal representation of the quote format */
2964 tmp = malloc(strlen(fmt)+1);
2965 pref_get_unescaped_pref(tmp, fmt);
2966 quote_fmt_scan_string(tmp);
2970 quote_fmt_scan_string(fmt);
2974 buf = quote_fmt_get_buffer();
2976 gint line = quote_fmt_get_line();
2977 alertpanel_error(err_msg, line);
2983 prev_autowrap = compose->autowrap;
2984 compose->autowrap = FALSE;
2986 mark = gtk_text_buffer_get_insert(buffer);
2987 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2988 if (g_utf8_validate(buf, -1, NULL)) {
2989 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2991 gchar *tmpout = NULL;
2992 tmpout = conv_codeset_strdup
2993 (buf, conv_get_locale_charset_str_no_utf8(),
2995 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2997 tmpout = g_malloc(strlen(buf)*2+1);
2998 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3000 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3004 cursor_pos = quote_fmt_get_cursor_pos();
3005 if (cursor_pos == -1)
3006 cursor_pos = gtk_text_iter_get_offset(&iter);
3007 compose->set_cursor_pos = cursor_pos;
3009 gtk_text_buffer_get_start_iter(buffer, &iter);
3010 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3011 gtk_text_buffer_place_cursor(buffer, &iter);
3013 compose->autowrap = prev_autowrap;
3014 if (compose->autowrap && rewrap)
3015 compose_wrap_all(compose);
3022 SIGNAL_UNBLOCK(buffer);
3024 procmsg_msginfo_free( dummyinfo );
3029 /* if ml_post is of type addr@host and from is of type
3030 * addr-anything@host, return TRUE
3032 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3034 gchar *left_ml = NULL;
3035 gchar *right_ml = NULL;
3036 gchar *left_from = NULL;
3037 gchar *right_from = NULL;
3038 gboolean result = FALSE;
3040 if (!ml_post || !from)
3043 left_ml = g_strdup(ml_post);
3044 if (strstr(left_ml, "@")) {
3045 right_ml = strstr(left_ml, "@")+1;
3046 *(strstr(left_ml, "@")) = '\0';
3049 left_from = g_strdup(from);
3050 if (strstr(left_from, "@")) {
3051 right_from = strstr(left_from, "@")+1;
3052 *(strstr(left_from, "@")) = '\0';
3055 if (left_ml && left_from && right_ml && right_from
3056 && !strncmp(left_from, left_ml, strlen(left_ml))
3057 && !strcmp(right_from, right_ml)) {
3066 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3067 gboolean to_all, gboolean to_ml,
3069 gboolean followup_and_reply_to)
3071 GSList *cc_list = NULL;
3074 gchar *replyto = NULL;
3075 gchar *ac_email = NULL;
3077 gboolean reply_to_ml = FALSE;
3078 gboolean default_reply_to = FALSE;
3080 cm_return_if_fail(compose->account != NULL);
3081 cm_return_if_fail(msginfo != NULL);
3083 reply_to_ml = to_ml && compose->ml_post;
3085 default_reply_to = msginfo->folder &&
3086 msginfo->folder->prefs->enable_default_reply_to;
3088 if (compose->account->protocol != A_NNTP) {
3089 if (msginfo && msginfo->folder && msginfo->folder->prefs) {
3090 if (msginfo->folder->prefs->enable_default_replyto) {
3091 compose_entry_append(compose, msginfo->folder->prefs->default_replyto,
3092 COMPOSE_REPLYTO, PREF_FOLDER);
3094 if (msginfo->folder->prefs->enable_default_bcc) {
3095 compose_entry_append(compose, msginfo->folder->prefs->default_bcc,
3096 COMPOSE_BCC, PREF_FOLDER);
3098 if (msginfo->folder->prefs->enable_default_cc) {
3099 compose_entry_append(compose, msginfo->folder->prefs->default_cc,
3100 COMPOSE_CC, PREF_FOLDER);
3103 if (reply_to_ml && !default_reply_to) {
3105 gboolean is_subscr = is_subscription(compose->ml_post,
3108 /* normal answer to ml post with a reply-to */
3109 compose_entry_append(compose,
3111 COMPOSE_TO, PREF_ML);
3112 if (compose->replyto)
3113 compose_entry_append(compose,
3115 COMPOSE_CC, PREF_ML);
3117 /* answer to subscription confirmation */
3118 if (compose->replyto)
3119 compose_entry_append(compose,
3121 COMPOSE_TO, PREF_ML);
3122 else if (msginfo->from)
3123 compose_entry_append(compose,
3125 COMPOSE_TO, PREF_ML);
3128 else if (!(to_all || to_sender) && default_reply_to) {
3129 compose_entry_append(compose,
3130 msginfo->folder->prefs->default_reply_to,
3131 COMPOSE_TO, PREF_FOLDER);
3132 compose_entry_mark_default_to(compose,
3133 msginfo->folder->prefs->default_reply_to);
3138 Xstrdup_a(tmp1, msginfo->from, return);
3139 extract_address(tmp1);
3140 if (to_all || to_sender ||
3141 !account_find_from_address(tmp1, FALSE))
3142 compose_entry_append(compose,
3143 (compose->replyto && !to_sender)
3144 ? compose->replyto :
3145 msginfo->from ? msginfo->from : "",
3146 COMPOSE_TO, PREF_NONE);
3147 else if (!to_all && !to_sender) {
3148 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3149 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3150 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3151 if (compose->replyto) {
3152 compose_entry_append(compose,
3154 COMPOSE_TO, PREF_NONE);
3156 compose_entry_append(compose,
3157 msginfo->from ? msginfo->from : "",
3158 COMPOSE_TO, PREF_NONE);
3161 /* replying to own mail, use original recp */
3162 compose_entry_append(compose,
3163 msginfo->to ? msginfo->to : "",
3164 COMPOSE_TO, PREF_NONE);
3165 compose_entry_append(compose,
3166 msginfo->cc ? msginfo->cc : "",
3167 COMPOSE_CC, PREF_NONE);
3172 if (to_sender || (compose->followup_to &&
3173 !strncmp(compose->followup_to, "poster", 6)))
3174 compose_entry_append
3176 (compose->replyto ? compose->replyto :
3177 msginfo->from ? msginfo->from : ""),
3178 COMPOSE_TO, PREF_NONE);
3180 else if (followup_and_reply_to || to_all) {
3181 compose_entry_append
3183 (compose->replyto ? compose->replyto :
3184 msginfo->from ? msginfo->from : ""),
3185 COMPOSE_TO, PREF_NONE);
3187 compose_entry_append
3189 compose->followup_to ? compose->followup_to :
3190 compose->newsgroups ? compose->newsgroups : "",
3191 COMPOSE_NEWSGROUPS, PREF_NONE);
3194 compose_entry_append
3196 compose->followup_to ? compose->followup_to :
3197 compose->newsgroups ? compose->newsgroups : "",
3198 COMPOSE_NEWSGROUPS, PREF_NONE);
3201 if (msginfo->subject && *msginfo->subject) {
3205 buf = p = g_strdup(msginfo->subject);
3206 p += subject_get_prefix_length(p);
3207 memmove(buf, p, strlen(p) + 1);
3209 buf2 = g_strdup_printf("Re: %s", buf);
3210 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3215 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3217 if (to_ml && compose->ml_post) return;
3218 if (!to_all || compose->account->protocol == A_NNTP) return;
3220 if (compose->replyto) {
3221 Xstrdup_a(replyto, compose->replyto, return);
3222 extract_address(replyto);
3224 if (msginfo->from) {
3225 Xstrdup_a(from, msginfo->from, return);
3226 extract_address(from);
3229 if (replyto && from)
3230 cc_list = address_list_append_with_comments(cc_list, from);
3231 if (to_all && msginfo->folder &&
3232 msginfo->folder->prefs->enable_default_reply_to)
3233 cc_list = address_list_append_with_comments(cc_list,
3234 msginfo->folder->prefs->default_reply_to);
3235 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3236 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3238 ac_email = g_utf8_strdown(compose->account->address, -1);
3241 for (cur = cc_list; cur != NULL; cur = cur->next) {
3242 gchar *addr = g_utf8_strdown(cur->data, -1);
3243 extract_address(addr);
3245 if (strcmp(ac_email, addr))
3246 compose_entry_append(compose, (gchar *)cur->data,
3247 COMPOSE_CC, PREF_NONE);
3249 debug_print("Cc address same as compose account's, ignoring\n");
3254 slist_free_strings(cc_list);
3255 g_slist_free(cc_list);
3261 #define SET_ENTRY(entry, str) \
3264 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3267 #define SET_ADDRESS(type, str) \
3270 compose_entry_append(compose, str, type, PREF_NONE); \
3273 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3275 cm_return_if_fail(msginfo != NULL);
3277 SET_ENTRY(subject_entry, msginfo->subject);
3278 SET_ENTRY(from_name, msginfo->from);
3279 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3280 SET_ADDRESS(COMPOSE_CC, compose->cc);
3281 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3282 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3283 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3284 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3285 SET_ADDRESS(COMPOSE_INREPLYTO, compose->inreplyto);
3287 compose_update_priority_menu_item(compose);
3288 compose_update_privacy_system_menu_item(compose, FALSE);
3289 compose_show_first_last_header(compose, TRUE);
3295 static void compose_insert_sig(Compose *compose, gboolean replace)
3297 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3298 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3300 GtkTextIter iter, iter_end;
3301 gint cur_pos, ins_pos;
3302 gboolean prev_autowrap;
3303 gboolean found = FALSE;
3304 gboolean exists = FALSE;
3306 cm_return_if_fail(compose->account != NULL);
3310 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3311 G_CALLBACK(compose_changed_cb),
3314 mark = gtk_text_buffer_get_insert(buffer);
3315 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3316 cur_pos = gtk_text_iter_get_offset (&iter);
3319 gtk_text_buffer_get_end_iter(buffer, &iter);
3321 exists = (compose->sig_str != NULL);
3324 GtkTextIter first_iter, start_iter, end_iter;
3326 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3328 if (!exists || compose->sig_str[0] == '\0')
3331 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3332 compose->signature_tag);
3335 /* include previous \n\n */
3336 gtk_text_iter_backward_chars(&first_iter, 1);
3337 start_iter = first_iter;
3338 end_iter = first_iter;
3340 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3341 compose->signature_tag);
3342 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3343 compose->signature_tag);
3345 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3351 g_free(compose->sig_str);
3352 compose->sig_str = account_get_signature_str(compose->account);
3354 cur_pos = gtk_text_iter_get_offset(&iter);
3356 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3357 g_free(compose->sig_str);
3358 compose->sig_str = NULL;
3360 if (compose->sig_inserted == FALSE)
3361 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3362 compose->sig_inserted = TRUE;
3364 cur_pos = gtk_text_iter_get_offset(&iter);
3365 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3367 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3368 gtk_text_iter_forward_chars(&iter, 1);
3369 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3370 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3372 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3373 cur_pos = gtk_text_buffer_get_char_count (buffer);
3376 /* put the cursor where it should be
3377 * either where the quote_fmt says, either where it was */
3378 if (compose->set_cursor_pos < 0)
3379 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3381 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3382 compose->set_cursor_pos);
3384 compose->set_cursor_pos = -1;
3385 gtk_text_buffer_place_cursor(buffer, &iter);
3386 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3387 G_CALLBACK(compose_changed_cb),
3393 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3396 GtkTextBuffer *buffer;
3399 const gchar *cur_encoding;
3400 gchar buf[BUFFSIZE];
3403 gboolean prev_autowrap;
3404 gboolean badtxt = FALSE;
3405 struct stat file_stat;
3408 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3410 /* get the size of the file we are about to insert */
3411 ret = g_stat(file, &file_stat);
3413 gchar *shortfile = g_path_get_basename(file);
3414 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3416 return COMPOSE_INSERT_NO_FILE;
3417 } else if (prefs_common.warn_large_insert == TRUE) {
3419 /* ask user for confirmation if the file is large */
3420 if (prefs_common.warn_large_insert_size < 0 ||
3421 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3425 msg = g_strdup_printf(_("You are about to insert a file of %s "
3426 "in the message body. Are you sure you want to do that?"),
3427 to_human_readable(file_stat.st_size));
3428 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3429 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3432 /* do we ask for confirmation next time? */
3433 if (aval & G_ALERTDISABLE) {
3434 /* no confirmation next time, disable feature in preferences */
3435 aval &= ~G_ALERTDISABLE;
3436 prefs_common.warn_large_insert = FALSE;
3439 /* abort file insertion if user canceled action */
3440 if (aval != G_ALERTALTERNATE) {
3441 return COMPOSE_INSERT_NO_FILE;
3447 if ((fp = g_fopen(file, "rb")) == NULL) {
3448 FILE_OP_ERROR(file, "fopen");
3449 return COMPOSE_INSERT_READ_ERROR;
3452 prev_autowrap = compose->autowrap;
3453 compose->autowrap = FALSE;
3455 text = GTK_TEXT_VIEW(compose->text);
3456 buffer = gtk_text_view_get_buffer(text);
3457 mark = gtk_text_buffer_get_insert(buffer);
3458 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3460 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3461 G_CALLBACK(text_inserted),
3464 cur_encoding = conv_get_locale_charset_str_no_utf8();
3466 while (fgets(buf, sizeof(buf), fp) != NULL) {
3469 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3470 str = g_strdup(buf);
3472 str = conv_codeset_strdup
3473 (buf, cur_encoding, CS_INTERNAL);
3476 /* strip <CR> if DOS/Windows file,
3477 replace <CR> with <LF> if Macintosh file. */
3480 if (len > 0 && str[len - 1] != '\n') {
3482 if (str[len] == '\r') str[len] = '\n';
3485 gtk_text_buffer_insert(buffer, &iter, str, -1);
3489 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3490 G_CALLBACK(text_inserted),
3492 compose->autowrap = prev_autowrap;
3493 if (compose->autowrap)
3494 compose_wrap_all(compose);
3499 return COMPOSE_INSERT_INVALID_CHARACTER;
3501 return COMPOSE_INSERT_SUCCESS;
3504 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3505 const gchar *filename,
3506 const gchar *content_type)
3514 GtkListStore *store;
3516 gboolean has_binary = FALSE;
3518 if (!is_file_exist(file)) {
3519 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3520 gboolean result = FALSE;
3521 if (file_from_uri && is_file_exist(file_from_uri)) {
3522 result = compose_attach_append(
3523 compose, file_from_uri,
3527 g_free(file_from_uri);
3530 alertpanel_error("File %s doesn't exist\n", filename);
3533 if ((size = get_file_size(file)) < 0) {
3534 alertpanel_error("Can't get file size of %s\n", filename);
3538 alertpanel_error(_("File %s is empty."), filename);
3541 if ((fp = g_fopen(file, "rb")) == NULL) {
3542 alertpanel_error(_("Can't read %s."), filename);
3547 ainfo = g_new0(AttachInfo, 1);
3548 auto_ainfo = g_auto_pointer_new_with_free
3549 (ainfo, (GFreeFunc) compose_attach_info_free);
3550 ainfo->file = g_strdup(file);
3553 ainfo->content_type = g_strdup(content_type);
3554 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3556 MsgFlags flags = {0, 0};
3558 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3559 ainfo->encoding = ENC_7BIT;
3561 ainfo->encoding = ENC_8BIT;
3563 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3564 if (msginfo && msginfo->subject)
3565 name = g_strdup(msginfo->subject);
3567 name = g_path_get_basename(filename ? filename : file);
3569 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3571 procmsg_msginfo_free(msginfo);
3573 if (!g_ascii_strncasecmp(content_type, "text", 4))
3574 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3576 ainfo->encoding = ENC_BASE64;
3577 name = g_path_get_basename(filename ? filename : file);
3578 ainfo->name = g_strdup(name);
3582 ainfo->content_type = procmime_get_mime_type(file);
3583 if (!ainfo->content_type) {
3584 ainfo->content_type =
3585 g_strdup("application/octet-stream");
3586 ainfo->encoding = ENC_BASE64;
3587 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3589 procmime_get_encoding_for_text_file(file, &has_binary);
3591 ainfo->encoding = ENC_BASE64;
3592 name = g_path_get_basename(filename ? filename : file);
3593 ainfo->name = g_strdup(name);
3597 if (ainfo->name != NULL
3598 && !strcmp(ainfo->name, ".")) {
3599 g_free(ainfo->name);
3603 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3604 g_free(ainfo->content_type);
3605 ainfo->content_type = g_strdup("application/octet-stream");
3608 ainfo->size = (goffset)size;
3609 size_text = to_human_readable((goffset)size);
3611 store = GTK_LIST_STORE(gtk_tree_view_get_model
3612 (GTK_TREE_VIEW(compose->attach_clist)));
3614 gtk_list_store_append(store, &iter);
3615 gtk_list_store_set(store, &iter,
3616 COL_MIMETYPE, ainfo->content_type,
3617 COL_SIZE, size_text,
3618 COL_NAME, ainfo->name,
3620 COL_AUTODATA, auto_ainfo,
3623 g_auto_pointer_free(auto_ainfo);
3624 compose_attach_update_label(compose);
3628 static void compose_use_signing(Compose *compose, gboolean use_signing)
3630 compose->use_signing = use_signing;
3631 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3634 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3636 compose->use_encryption = use_encryption;
3637 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3640 #define NEXT_PART_NOT_CHILD(info) \
3642 node = info->node; \
3643 while (node->children) \
3644 node = g_node_last_child(node); \
3645 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3648 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3652 MimeInfo *firsttext = NULL;
3653 MimeInfo *encrypted = NULL;
3656 const gchar *partname = NULL;
3658 mimeinfo = procmime_scan_message(msginfo);
3659 if (!mimeinfo) return;
3661 if (mimeinfo->node->children == NULL) {
3662 procmime_mimeinfo_free_all(mimeinfo);
3666 /* find first content part */
3667 child = (MimeInfo *) mimeinfo->node->children->data;
3668 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3669 child = (MimeInfo *)child->node->children->data;
3672 if (child->type == MIMETYPE_TEXT) {
3674 debug_print("First text part found\n");
3675 } else if (compose->mode == COMPOSE_REEDIT &&
3676 child->type == MIMETYPE_APPLICATION &&
3677 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3678 encrypted = (MimeInfo *)child->node->parent->data;
3681 child = (MimeInfo *) mimeinfo->node->children->data;
3682 while (child != NULL) {
3685 if (child == encrypted) {
3686 /* skip this part of tree */
3687 NEXT_PART_NOT_CHILD(child);
3691 if (child->type == MIMETYPE_MULTIPART) {
3692 /* get the actual content */
3693 child = procmime_mimeinfo_next(child);
3697 if (child == firsttext) {
3698 child = procmime_mimeinfo_next(child);
3702 outfile = procmime_get_tmp_file_name(child);
3703 if ((err = procmime_get_part(outfile, child)) < 0)
3704 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3706 gchar *content_type;
3708 content_type = procmime_get_content_type_str(child->type, child->subtype);
3710 /* if we meet a pgp signature, we don't attach it, but
3711 * we force signing. */
3712 if ((strcmp(content_type, "application/pgp-signature") &&
3713 strcmp(content_type, "application/pkcs7-signature") &&
3714 strcmp(content_type, "application/x-pkcs7-signature"))
3715 || compose->mode == COMPOSE_REDIRECT) {
3716 partname = procmime_mimeinfo_get_parameter(child, "filename");
3717 if (partname == NULL)
3718 partname = procmime_mimeinfo_get_parameter(child, "name");
3719 if (partname == NULL)
3721 compose_attach_append(compose, outfile,
3722 partname, content_type);
3724 compose_force_signing(compose, compose->account, NULL);
3726 g_free(content_type);
3729 NEXT_PART_NOT_CHILD(child);
3731 procmime_mimeinfo_free_all(mimeinfo);
3734 #undef NEXT_PART_NOT_CHILD
3739 WAIT_FOR_INDENT_CHAR,
3740 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3743 /* return indent length, we allow:
3744 indent characters followed by indent characters or spaces/tabs,
3745 alphabets and numbers immediately followed by indent characters,
3746 and the repeating sequences of the above
3747 If quote ends with multiple spaces, only the first one is included. */
3748 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3749 const GtkTextIter *start, gint *len)
3751 GtkTextIter iter = *start;
3755 IndentState state = WAIT_FOR_INDENT_CHAR;
3758 gint alnum_count = 0;
3759 gint space_count = 0;
3762 if (prefs_common.quote_chars == NULL) {
3766 while (!gtk_text_iter_ends_line(&iter)) {
3767 wc = gtk_text_iter_get_char(&iter);
3768 if (g_unichar_iswide(wc))
3770 clen = g_unichar_to_utf8(wc, ch);
3774 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3775 is_space = g_unichar_isspace(wc);
3777 if (state == WAIT_FOR_INDENT_CHAR) {
3778 if (!is_indent && !g_unichar_isalnum(wc))
3781 quote_len += alnum_count + space_count + 1;
3782 alnum_count = space_count = 0;
3783 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3786 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3787 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3791 else if (is_indent) {
3792 quote_len += alnum_count + space_count + 1;
3793 alnum_count = space_count = 0;
3796 state = WAIT_FOR_INDENT_CHAR;
3800 gtk_text_iter_forward_char(&iter);
3803 if (quote_len > 0 && space_count > 0)
3809 if (quote_len > 0) {
3811 gtk_text_iter_forward_chars(&iter, quote_len);
3812 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3818 /* return >0 if the line is itemized */
3819 static int compose_itemized_length(GtkTextBuffer *buffer,
3820 const GtkTextIter *start)
3822 GtkTextIter iter = *start;
3827 if (gtk_text_iter_ends_line(&iter))
3832 wc = gtk_text_iter_get_char(&iter);
3833 if (!g_unichar_isspace(wc))
3835 gtk_text_iter_forward_char(&iter);
3836 if (gtk_text_iter_ends_line(&iter))
3840 clen = g_unichar_to_utf8(wc, ch);
3844 if (!strchr("*-+", ch[0]))
3847 gtk_text_iter_forward_char(&iter);
3848 if (gtk_text_iter_ends_line(&iter))
3850 wc = gtk_text_iter_get_char(&iter);
3851 if (g_unichar_isspace(wc)) {
3857 /* return the string at the start of the itemization */
3858 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3859 const GtkTextIter *start)
3861 GtkTextIter iter = *start;
3864 GString *item_chars = g_string_new("");
3867 if (gtk_text_iter_ends_line(&iter))
3872 wc = gtk_text_iter_get_char(&iter);
3873 if (!g_unichar_isspace(wc))
3875 gtk_text_iter_forward_char(&iter);
3876 if (gtk_text_iter_ends_line(&iter))
3878 g_string_append_unichar(item_chars, wc);
3881 str = item_chars->str;
3882 g_string_free(item_chars, FALSE);
3886 /* return the number of spaces at a line's start */
3887 static int compose_left_offset_length(GtkTextBuffer *buffer,
3888 const GtkTextIter *start)
3890 GtkTextIter iter = *start;
3893 if (gtk_text_iter_ends_line(&iter))
3897 wc = gtk_text_iter_get_char(&iter);
3898 if (!g_unichar_isspace(wc))
3901 gtk_text_iter_forward_char(&iter);
3902 if (gtk_text_iter_ends_line(&iter))
3906 gtk_text_iter_forward_char(&iter);
3907 if (gtk_text_iter_ends_line(&iter))
3912 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3913 const GtkTextIter *start,
3914 GtkTextIter *break_pos,
3918 GtkTextIter iter = *start, line_end = *start;
3919 PangoLogAttr *attrs;
3926 gboolean can_break = FALSE;
3927 gboolean do_break = FALSE;
3928 gboolean was_white = FALSE;
3929 gboolean prev_dont_break = FALSE;
3931 gtk_text_iter_forward_to_line_end(&line_end);
3932 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3933 len = g_utf8_strlen(str, -1);
3937 g_warning("compose_get_line_break_pos: len = 0!\n");
3941 /* g_print("breaking line: %d: %s (len = %d)\n",
3942 gtk_text_iter_get_line(&iter), str, len); */
3944 attrs = g_new(PangoLogAttr, len + 1);
3946 pango_default_break(str, -1, NULL, attrs, len + 1);
3950 /* skip quote and leading spaces */
3951 for (i = 0; *p != '\0' && i < len; i++) {
3954 wc = g_utf8_get_char(p);
3955 if (i >= quote_len && !g_unichar_isspace(wc))
3957 if (g_unichar_iswide(wc))
3959 else if (*p == '\t')
3963 p = g_utf8_next_char(p);
3966 for (; *p != '\0' && i < len; i++) {
3967 PangoLogAttr *attr = attrs + i;
3971 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3974 was_white = attr->is_white;
3976 /* don't wrap URI */
3977 if ((uri_len = get_uri_len(p)) > 0) {
3979 if (pos > 0 && col > max_col) {
3989 wc = g_utf8_get_char(p);
3990 if (g_unichar_iswide(wc)) {
3992 if (prev_dont_break && can_break && attr->is_line_break)
3994 } else if (*p == '\t')
3998 if (pos > 0 && col > max_col) {
4003 if (*p == '-' || *p == '/')
4004 prev_dont_break = TRUE;
4006 prev_dont_break = FALSE;
4008 p = g_utf8_next_char(p);
4012 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4017 *break_pos = *start;
4018 gtk_text_iter_set_line_offset(break_pos, pos);
4023 static gboolean compose_join_next_line(Compose *compose,
4024 GtkTextBuffer *buffer,
4026 const gchar *quote_str)
4028 GtkTextIter iter_ = *iter, cur, prev, next, end;
4029 PangoLogAttr attrs[3];
4031 gchar *next_quote_str;
4034 gboolean keep_cursor = FALSE;
4036 if (!gtk_text_iter_forward_line(&iter_) ||
4037 gtk_text_iter_ends_line(&iter_)) {
4040 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4042 if ((quote_str || next_quote_str) &&
4043 strcmp2(quote_str, next_quote_str) != 0) {
4044 g_free(next_quote_str);
4047 g_free(next_quote_str);
4050 if (quote_len > 0) {
4051 gtk_text_iter_forward_chars(&end, quote_len);
4052 if (gtk_text_iter_ends_line(&end)) {
4057 /* don't join itemized lines */
4058 if (compose_itemized_length(buffer, &end) > 0) {
4062 /* don't join signature separator */
4063 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4066 /* delete quote str */
4068 gtk_text_buffer_delete(buffer, &iter_, &end);
4070 /* don't join line breaks put by the user */
4072 gtk_text_iter_backward_char(&cur);
4073 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4074 gtk_text_iter_forward_char(&cur);
4078 gtk_text_iter_forward_char(&cur);
4079 /* delete linebreak and extra spaces */
4080 while (gtk_text_iter_backward_char(&cur)) {
4081 wc1 = gtk_text_iter_get_char(&cur);
4082 if (!g_unichar_isspace(wc1))
4087 while (!gtk_text_iter_ends_line(&cur)) {
4088 wc1 = gtk_text_iter_get_char(&cur);
4089 if (!g_unichar_isspace(wc1))
4091 gtk_text_iter_forward_char(&cur);
4094 if (!gtk_text_iter_equal(&prev, &next)) {
4097 mark = gtk_text_buffer_get_insert(buffer);
4098 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4099 if (gtk_text_iter_equal(&prev, &cur))
4101 gtk_text_buffer_delete(buffer, &prev, &next);
4105 /* insert space if required */
4106 gtk_text_iter_backward_char(&prev);
4107 wc1 = gtk_text_iter_get_char(&prev);
4108 wc2 = gtk_text_iter_get_char(&next);
4109 gtk_text_iter_forward_char(&next);
4110 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4111 pango_default_break(str, -1, NULL, attrs, 3);
4112 if (!attrs[1].is_line_break ||
4113 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4114 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4116 gtk_text_iter_backward_char(&iter_);
4117 gtk_text_buffer_place_cursor(buffer, &iter_);
4126 #define ADD_TXT_POS(bp_, ep_, pti_) \
4127 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4128 last = last->next; \
4129 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4130 last->next = NULL; \
4132 g_warning("alloc error scanning URIs\n"); \
4135 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4137 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4138 GtkTextBuffer *buffer;
4139 GtkTextIter iter, break_pos, end_of_line;
4140 gchar *quote_str = NULL;
4142 gboolean wrap_quote = prefs_common.linewrap_quote;
4143 gboolean prev_autowrap = compose->autowrap;
4144 gint startq_offset = -1, noq_offset = -1;
4145 gint uri_start = -1, uri_stop = -1;
4146 gint nouri_start = -1, nouri_stop = -1;
4147 gint num_blocks = 0;
4148 gint quotelevel = -1;
4149 gboolean modified = force;
4150 gboolean removed = FALSE;
4151 gboolean modified_before_remove = FALSE;
4153 gboolean start = TRUE;
4154 gint itemized_len = 0, rem_item_len = 0;
4155 gchar *itemized_chars = NULL;
4156 gboolean item_continuation = FALSE;
4161 if (compose->draft_timeout_tag == -2) {
4165 compose->autowrap = FALSE;
4167 buffer = gtk_text_view_get_buffer(text);
4168 undo_wrapping(compose->undostruct, TRUE);
4173 mark = gtk_text_buffer_get_insert(buffer);
4174 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4178 if (compose->draft_timeout_tag == -2) {
4179 if (gtk_text_iter_ends_line(&iter)) {
4180 while (gtk_text_iter_ends_line(&iter) &&
4181 gtk_text_iter_forward_line(&iter))
4184 while (gtk_text_iter_backward_line(&iter)) {
4185 if (gtk_text_iter_ends_line(&iter)) {
4186 gtk_text_iter_forward_line(&iter);
4192 /* move to line start */
4193 gtk_text_iter_set_line_offset(&iter, 0);
4196 itemized_len = compose_itemized_length(buffer, &iter);
4198 if (!itemized_len) {
4199 itemized_len = compose_left_offset_length(buffer, &iter);
4200 item_continuation = TRUE;
4204 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4206 /* go until paragraph end (empty line) */
4207 while (start || !gtk_text_iter_ends_line(&iter)) {
4208 gchar *scanpos = NULL;
4209 /* parse table - in order of priority */
4211 const gchar *needle; /* token */
4213 /* token search function */
4214 gchar *(*search) (const gchar *haystack,
4215 const gchar *needle);
4216 /* part parsing function */
4217 gboolean (*parse) (const gchar *start,
4218 const gchar *scanpos,
4222 /* part to URI function */
4223 gchar *(*build_uri) (const gchar *bp,
4227 static struct table parser[] = {
4228 {"http://", strcasestr, get_uri_part, make_uri_string},
4229 {"https://", strcasestr, get_uri_part, make_uri_string},
4230 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4231 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4232 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4233 {"www.", strcasestr, get_uri_part, make_http_string},
4234 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4235 {"@", strcasestr, get_email_part, make_email_string}
4237 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4238 gint last_index = PARSE_ELEMS;
4240 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4244 if (!prev_autowrap && num_blocks == 0) {
4246 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4247 G_CALLBACK(text_inserted),
4250 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4253 uri_start = uri_stop = -1;
4255 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4258 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4259 if (startq_offset == -1)
4260 startq_offset = gtk_text_iter_get_offset(&iter);
4261 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4262 if (quotelevel > 2) {
4263 /* recycle colors */
4264 if (prefs_common.recycle_quote_colors)
4273 if (startq_offset == -1)
4274 noq_offset = gtk_text_iter_get_offset(&iter);
4278 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4281 if (gtk_text_iter_ends_line(&iter)) {
4283 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4284 prefs_common.linewrap_len,
4286 GtkTextIter prev, next, cur;
4287 if (prev_autowrap != FALSE || force) {
4288 compose->automatic_break = TRUE;
4290 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4291 compose->automatic_break = FALSE;
4292 if (itemized_len && compose->autoindent) {
4293 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4294 if (!item_continuation)
4295 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4297 } else if (quote_str && wrap_quote) {
4298 compose->automatic_break = TRUE;
4300 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4301 compose->automatic_break = FALSE;
4302 if (itemized_len && compose->autoindent) {
4303 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4304 if (!item_continuation)
4305 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4309 /* remove trailing spaces */
4311 rem_item_len = itemized_len;
4312 while (compose->autoindent && rem_item_len-- > 0)
4313 gtk_text_iter_backward_char(&cur);
4314 gtk_text_iter_backward_char(&cur);
4317 while (!gtk_text_iter_starts_line(&cur)) {
4320 gtk_text_iter_backward_char(&cur);
4321 wc = gtk_text_iter_get_char(&cur);
4322 if (!g_unichar_isspace(wc))
4326 if (!gtk_text_iter_equal(&prev, &next)) {
4327 gtk_text_buffer_delete(buffer, &prev, &next);
4329 gtk_text_iter_forward_char(&break_pos);
4333 gtk_text_buffer_insert(buffer, &break_pos,
4337 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4339 /* move iter to current line start */
4340 gtk_text_iter_set_line_offset(&iter, 0);
4347 /* move iter to next line start */
4353 if (!prev_autowrap && num_blocks > 0) {
4355 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4356 G_CALLBACK(text_inserted),
4360 while (!gtk_text_iter_ends_line(&end_of_line)) {
4361 gtk_text_iter_forward_char(&end_of_line);
4363 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4365 nouri_start = gtk_text_iter_get_offset(&iter);
4366 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4368 walk_pos = gtk_text_iter_get_offset(&iter);
4369 /* FIXME: this looks phony. scanning for anything in the parse table */
4370 for (n = 0; n < PARSE_ELEMS; n++) {
4373 tmp = parser[n].search(walk, parser[n].needle);
4375 if (scanpos == NULL || tmp < scanpos) {
4384 /* check if URI can be parsed */
4385 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4386 (const gchar **)&ep, FALSE)
4387 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4391 strlen(parser[last_index].needle);
4394 uri_start = walk_pos + (bp - o_walk);
4395 uri_stop = walk_pos + (ep - o_walk);
4399 gtk_text_iter_forward_line(&iter);
4402 if (startq_offset != -1) {
4403 GtkTextIter startquote, endquote;
4404 gtk_text_buffer_get_iter_at_offset(
4405 buffer, &startquote, startq_offset);
4408 switch (quotelevel) {
4410 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4411 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4412 gtk_text_buffer_apply_tag_by_name(
4413 buffer, "quote0", &startquote, &endquote);
4414 gtk_text_buffer_remove_tag_by_name(
4415 buffer, "quote1", &startquote, &endquote);
4416 gtk_text_buffer_remove_tag_by_name(
4417 buffer, "quote2", &startquote, &endquote);
4422 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4423 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4424 gtk_text_buffer_apply_tag_by_name(
4425 buffer, "quote1", &startquote, &endquote);
4426 gtk_text_buffer_remove_tag_by_name(
4427 buffer, "quote0", &startquote, &endquote);
4428 gtk_text_buffer_remove_tag_by_name(
4429 buffer, "quote2", &startquote, &endquote);
4434 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4435 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4436 gtk_text_buffer_apply_tag_by_name(
4437 buffer, "quote2", &startquote, &endquote);
4438 gtk_text_buffer_remove_tag_by_name(
4439 buffer, "quote0", &startquote, &endquote);
4440 gtk_text_buffer_remove_tag_by_name(
4441 buffer, "quote1", &startquote, &endquote);
4447 } else if (noq_offset != -1) {
4448 GtkTextIter startnoquote, endnoquote;
4449 gtk_text_buffer_get_iter_at_offset(
4450 buffer, &startnoquote, noq_offset);
4453 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4454 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4455 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4456 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4457 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4458 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4459 gtk_text_buffer_remove_tag_by_name(
4460 buffer, "quote0", &startnoquote, &endnoquote);
4461 gtk_text_buffer_remove_tag_by_name(
4462 buffer, "quote1", &startnoquote, &endnoquote);
4463 gtk_text_buffer_remove_tag_by_name(
4464 buffer, "quote2", &startnoquote, &endnoquote);
4470 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4471 GtkTextIter nouri_start_iter, nouri_end_iter;
4472 gtk_text_buffer_get_iter_at_offset(
4473 buffer, &nouri_start_iter, nouri_start);
4474 gtk_text_buffer_get_iter_at_offset(
4475 buffer, &nouri_end_iter, nouri_stop);
4476 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4477 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4478 gtk_text_buffer_remove_tag_by_name(
4479 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4480 modified_before_remove = modified;
4485 if (uri_start >= 0 && uri_stop > 0) {
4486 GtkTextIter uri_start_iter, uri_end_iter, back;
4487 gtk_text_buffer_get_iter_at_offset(
4488 buffer, &uri_start_iter, uri_start);
4489 gtk_text_buffer_get_iter_at_offset(
4490 buffer, &uri_end_iter, uri_stop);
4491 back = uri_end_iter;
4492 gtk_text_iter_backward_char(&back);
4493 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4494 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4495 gtk_text_buffer_apply_tag_by_name(
4496 buffer, "link", &uri_start_iter, &uri_end_iter);
4498 if (removed && !modified_before_remove) {
4504 // debug_print("not modified, out after %d lines\n", lines);
4508 // debug_print("modified, out after %d lines\n", lines);
4510 g_free(itemized_chars);
4513 undo_wrapping(compose->undostruct, FALSE);
4514 compose->autowrap = prev_autowrap;
4519 void compose_action_cb(void *data)
4521 Compose *compose = (Compose *)data;
4522 compose_wrap_all(compose);
4525 static void compose_wrap_all(Compose *compose)
4527 compose_wrap_all_full(compose, FALSE);
4530 static void compose_wrap_all_full(Compose *compose, gboolean force)
4532 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4533 GtkTextBuffer *buffer;
4535 gboolean modified = TRUE;
4537 buffer = gtk_text_view_get_buffer(text);
4539 gtk_text_buffer_get_start_iter(buffer, &iter);
4540 while (!gtk_text_iter_is_end(&iter) && modified)
4541 modified = compose_beautify_paragraph(compose, &iter, force);
4545 static void compose_set_title(Compose *compose)
4551 edited = compose->modified ? _(" [Edited]") : "";
4553 subject = gtk_editable_get_chars(
4554 GTK_EDITABLE(compose->subject_entry), 0, -1);
4556 #ifndef GENERIC_UMPC
4557 if (subject && strlen(subject))
4558 str = g_strdup_printf(_("%s - Compose message%s"),
4561 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4563 str = g_strdup(_("Compose message"));
4566 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4572 * compose_current_mail_account:
4574 * Find a current mail account (the currently selected account, or the
4575 * default account, if a news account is currently selected). If a
4576 * mail account cannot be found, display an error message.
4578 * Return value: Mail account, or NULL if not found.
4580 static PrefsAccount *
4581 compose_current_mail_account(void)
4585 if (cur_account && cur_account->protocol != A_NNTP)
4588 ac = account_get_default();
4589 if (!ac || ac->protocol == A_NNTP) {
4590 alertpanel_error(_("Account for sending mail is not specified.\n"
4591 "Please select a mail account before sending."));
4598 #define QUOTE_IF_REQUIRED(out, str) \
4600 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4604 len = strlen(str) + 3; \
4605 if ((__tmp = alloca(len)) == NULL) { \
4606 g_warning("can't allocate memory\n"); \
4607 g_string_free(header, TRUE); \
4610 g_snprintf(__tmp, len, "\"%s\"", str); \
4615 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4616 g_warning("can't allocate memory\n"); \
4617 g_string_free(header, TRUE); \
4620 strcpy(__tmp, str); \
4626 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4628 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4632 len = strlen(str) + 3; \
4633 if ((__tmp = alloca(len)) == NULL) { \
4634 g_warning("can't allocate memory\n"); \
4637 g_snprintf(__tmp, len, "\"%s\"", str); \
4642 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4643 g_warning("can't allocate memory\n"); \
4646 strcpy(__tmp, str); \
4652 static void compose_select_account(Compose *compose, PrefsAccount *account,
4655 gchar *from = NULL, *header;
4656 ComposeHeaderEntry *header_entry;
4658 cm_return_if_fail(account != NULL);
4660 compose->account = account;
4661 if (account->name && *account->name) {
4663 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4664 from = g_strdup_printf("%s <%s>",
4665 buf, account->address);
4666 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4668 from = g_strdup_printf("<%s>",
4670 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4675 compose_set_title(compose);
4677 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4678 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4680 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4681 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4682 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4684 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4686 activate_privacy_system(compose, account, FALSE);
4688 if (!init && compose->mode != COMPOSE_REDIRECT) {
4689 undo_block(compose->undostruct);
4690 compose_insert_sig(compose, TRUE);
4691 undo_unblock(compose->undostruct);
4694 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4695 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4697 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4698 if (account->protocol == A_NNTP) {
4699 if (!strcmp(header, _("To:")))
4700 combobox_select_by_text(
4701 GTK_COMBO_BOX(header_entry->combo),
4704 if (!strcmp(header, _("Newsgroups:")))
4705 combobox_select_by_text(
4706 GTK_COMBO_BOX(header_entry->combo),
4714 /* use account's dict info if set */
4715 if (compose->gtkaspell) {
4716 if (account->enable_default_dictionary)
4717 gtkaspell_change_dict(compose->gtkaspell,
4718 account->default_dictionary, FALSE);
4719 if (account->enable_default_alt_dictionary)
4720 gtkaspell_change_alt_dict(compose->gtkaspell,
4721 account->default_alt_dictionary);
4722 if (account->enable_default_dictionary
4723 || account->enable_default_alt_dictionary)
4724 compose_spell_menu_changed(compose);
4729 gboolean compose_check_for_valid_recipient(Compose *compose) {
4730 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4731 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4732 gboolean recipient_found = FALSE;
4736 /* free to and newsgroup list */
4737 slist_free_strings(compose->to_list);
4738 g_slist_free(compose->to_list);
4739 compose->to_list = NULL;
4741 slist_free_strings(compose->newsgroup_list);
4742 g_slist_free(compose->newsgroup_list);
4743 compose->newsgroup_list = NULL;
4745 /* search header entries for to and newsgroup entries */
4746 for (list = compose->header_list; list; list = list->next) {
4749 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4750 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4753 if (entry[0] != '\0') {
4754 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4755 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4756 compose->to_list = address_list_append(compose->to_list, entry);
4757 recipient_found = TRUE;
4760 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4761 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4762 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4763 recipient_found = TRUE;
4770 return recipient_found;
4773 static gboolean compose_check_for_set_recipients(Compose *compose)
4775 if (compose->account->set_autocc && compose->account->auto_cc) {
4776 gboolean found_other = FALSE;
4778 /* search header entries for to and newsgroup entries */
4779 for (list = compose->header_list; list; list = list->next) {
4782 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4783 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4786 if (strcmp(entry, compose->account->auto_cc)
4787 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4797 if (compose->batch) {
4798 gtk_widget_show_all(compose->window);
4800 aval = alertpanel(_("Send"),
4801 _("The only recipient is the default CC address. Send anyway?"),
4802 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4803 if (aval != G_ALERTALTERNATE)
4807 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4808 gboolean found_other = FALSE;
4810 /* search header entries for to and newsgroup entries */
4811 for (list = compose->header_list; list; list = list->next) {
4814 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4815 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4818 if (strcmp(entry, compose->account->auto_bcc)
4819 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4829 if (compose->batch) {
4830 gtk_widget_show_all(compose->window);
4832 aval = alertpanel(_("Send"),
4833 _("The only recipient is the default BCC address. Send anyway?"),
4834 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4835 if (aval != G_ALERTALTERNATE)
4842 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4846 if (compose_check_for_valid_recipient(compose) == FALSE) {
4847 if (compose->batch) {
4848 gtk_widget_show_all(compose->window);
4850 alertpanel_error(_("Recipient is not specified."));
4854 if (compose_check_for_set_recipients(compose) == FALSE) {
4858 if (!compose->batch) {
4859 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4860 if (*str == '\0' && check_everything == TRUE &&
4861 compose->mode != COMPOSE_REDIRECT) {
4863 gchar *button_label;
4866 if (compose->sending)
4867 button_label = _("+_Send");
4869 button_label = _("+_Queue");
4870 message = g_strdup_printf(_("Subject is empty. %s"),
4871 compose->sending?_("Send it anyway?"):
4872 _("Queue it anyway?"));
4874 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4875 GTK_STOCK_CANCEL, button_label, NULL);
4877 if (aval != G_ALERTALTERNATE)
4882 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4888 gint compose_send(Compose *compose)
4891 FolderItem *folder = NULL;
4893 gchar *msgpath = NULL;
4894 gboolean discard_window = FALSE;
4895 gchar *errstr = NULL;
4896 gchar *tmsgid = NULL;
4897 MainWindow *mainwin = mainwindow_get_mainwindow();
4898 gboolean queued_removed = FALSE;
4900 if (prefs_common.send_dialog_invisible
4901 || compose->batch == TRUE)
4902 discard_window = TRUE;
4904 compose_allow_user_actions (compose, FALSE);
4905 compose->sending = TRUE;
4907 if (compose_check_entries(compose, TRUE) == FALSE) {
4908 if (compose->batch) {
4909 gtk_widget_show_all(compose->window);
4915 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4918 if (compose->batch) {
4919 gtk_widget_show_all(compose->window);
4922 alertpanel_error(_("Could not queue message for sending:\n\n"
4923 "Charset conversion failed."));
4924 } else if (val == -5) {
4925 alertpanel_error(_("Could not queue message for sending:\n\n"
4926 "Couldn't get recipient encryption key."));
4927 } else if (val == -6) {
4929 } else if (val == -3) {
4930 if (privacy_peek_error())
4931 alertpanel_error(_("Could not queue message for sending:\n\n"
4932 "Signature failed: %s"), privacy_get_error());
4933 } else if (val == -2 && errno != 0) {
4934 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4936 alertpanel_error(_("Could not queue message for sending."));
4941 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4942 if (discard_window) {
4943 compose->sending = FALSE;
4944 compose_close(compose);
4945 /* No more compose access in the normal codepath
4946 * after this point! */
4951 alertpanel_error(_("The message was queued but could not be "
4952 "sent.\nUse \"Send queued messages\" from "
4953 "the main window to retry."));
4954 if (!discard_window) {
4961 if (msgpath == NULL) {
4962 msgpath = folder_item_fetch_msg(folder, msgnum);
4963 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4966 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4967 claws_unlink(msgpath);
4970 if (!discard_window) {
4972 if (!queued_removed)
4973 folder_item_remove_msg(folder, msgnum);
4974 folder_item_scan(folder);
4976 /* make sure we delete that */
4977 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4979 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4980 folder_item_remove_msg(folder, tmp->msgnum);
4981 procmsg_msginfo_free(tmp);
4988 if (!queued_removed)
4989 folder_item_remove_msg(folder, msgnum);
4990 folder_item_scan(folder);
4992 /* make sure we delete that */
4993 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4995 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4996 folder_item_remove_msg(folder, tmp->msgnum);
4997 procmsg_msginfo_free(tmp);
5000 if (!discard_window) {
5001 compose->sending = FALSE;
5002 compose_allow_user_actions (compose, TRUE);
5003 compose_close(compose);
5007 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5008 "the main window to retry."), errstr);
5011 alertpanel_error_log(_("The message was queued but could not be "
5012 "sent.\nUse \"Send queued messages\" from "
5013 "the main window to retry."));
5015 if (!discard_window) {
5024 toolbar_main_set_sensitive(mainwin);
5025 main_window_set_menu_sensitive(mainwin);
5031 compose_allow_user_actions (compose, TRUE);
5032 compose->sending = FALSE;
5033 compose->modified = TRUE;
5034 toolbar_main_set_sensitive(mainwin);
5035 main_window_set_menu_sensitive(mainwin);
5040 static gboolean compose_use_attach(Compose *compose)
5042 GtkTreeModel *model = gtk_tree_view_get_model
5043 (GTK_TREE_VIEW(compose->attach_clist));
5044 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5047 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5050 gchar buf[BUFFSIZE];
5052 gboolean first_to_address;
5053 gboolean first_cc_address;
5055 ComposeHeaderEntry *headerentry;
5056 const gchar *headerentryname;
5057 const gchar *cc_hdr;
5058 const gchar *to_hdr;
5059 gboolean err = FALSE;
5061 debug_print("Writing redirect header\n");
5063 cc_hdr = prefs_common_translated_header_name("Cc:");
5064 to_hdr = prefs_common_translated_header_name("To:");
5066 first_to_address = TRUE;
5067 for (list = compose->header_list; list; list = list->next) {
5068 headerentry = ((ComposeHeaderEntry *)list->data);
5069 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5071 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5072 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5073 Xstrdup_a(str, entstr, return -1);
5075 if (str[0] != '\0') {
5076 compose_convert_header
5077 (compose, buf, sizeof(buf), str,
5078 strlen("Resent-To") + 2, TRUE);
5080 if (first_to_address) {
5081 err |= (fprintf(fp, "Resent-To: ") < 0);
5082 first_to_address = FALSE;
5084 err |= (fprintf(fp, ",") < 0);
5086 err |= (fprintf(fp, "%s", buf) < 0);
5090 if (!first_to_address) {
5091 err |= (fprintf(fp, "\n") < 0);
5094 first_cc_address = TRUE;
5095 for (list = compose->header_list; list; list = list->next) {
5096 headerentry = ((ComposeHeaderEntry *)list->data);
5097 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5099 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5100 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5101 Xstrdup_a(str, strg, return -1);
5103 if (str[0] != '\0') {
5104 compose_convert_header
5105 (compose, buf, sizeof(buf), str,
5106 strlen("Resent-Cc") + 2, TRUE);
5108 if (first_cc_address) {
5109 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5110 first_cc_address = FALSE;
5112 err |= (fprintf(fp, ",") < 0);
5114 err |= (fprintf(fp, "%s", buf) < 0);
5118 if (!first_cc_address) {
5119 err |= (fprintf(fp, "\n") < 0);
5122 return (err ? -1:0);
5125 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5127 gchar buf[BUFFSIZE];
5129 const gchar *entstr;
5130 /* struct utsname utsbuf; */
5131 gboolean err = FALSE;
5133 cm_return_val_if_fail(fp != NULL, -1);
5134 cm_return_val_if_fail(compose->account != NULL, -1);
5135 cm_return_val_if_fail(compose->account->address != NULL, -1);
5138 get_rfc822_date(buf, sizeof(buf));
5139 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5142 if (compose->account->name && *compose->account->name) {
5143 compose_convert_header
5144 (compose, buf, sizeof(buf), compose->account->name,
5145 strlen("From: "), TRUE);
5146 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5147 buf, compose->account->address) < 0);
5149 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5152 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5153 if (*entstr != '\0') {
5154 Xstrdup_a(str, entstr, return -1);
5157 compose_convert_header(compose, buf, sizeof(buf), str,
5158 strlen("Subject: "), FALSE);
5159 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5163 /* Resent-Message-ID */
5164 if (compose->account->set_domain && compose->account->domain) {
5165 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5166 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5167 g_snprintf(buf, sizeof(buf), "%s",
5168 strchr(compose->account->address, '@') ?
5169 strchr(compose->account->address, '@')+1 :
5170 compose->account->address);
5172 g_snprintf(buf, sizeof(buf), "%s", "");
5175 if (compose->account->gen_msgid) {
5177 if (compose->account->msgid_with_addr) {
5178 addr = compose->account->address;
5180 generate_msgid(buf, sizeof(buf), addr);
5181 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5182 compose->msgid = g_strdup(buf);
5184 compose->msgid = NULL;
5187 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5190 /* separator between header and body */
5191 err |= (fputs("\n", fp) == EOF);
5193 return (err ? -1:0);
5196 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5200 gchar buf[BUFFSIZE];
5202 gboolean skip = FALSE;
5203 gboolean err = FALSE;
5204 gchar *not_included[]={
5205 "Return-Path:", "Delivered-To:", "Received:",
5206 "Subject:", "X-UIDL:", "AF:",
5207 "NF:", "PS:", "SRH:",
5208 "SFN:", "DSR:", "MID:",
5209 "CFG:", "PT:", "S:",
5210 "RQ:", "SSV:", "NSV:",
5211 "SSH:", "R:", "MAID:",
5212 "NAID:", "RMID:", "FMID:",
5213 "SCF:", "RRCPT:", "NG:",
5214 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5215 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5216 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5217 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5218 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5221 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5222 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5226 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5228 for (i = 0; not_included[i] != NULL; i++) {
5229 if (g_ascii_strncasecmp(buf, not_included[i],
5230 strlen(not_included[i])) == 0) {
5237 if (fputs(buf, fdest) == -1)
5240 if (!prefs_common.redirect_keep_from) {
5241 if (g_ascii_strncasecmp(buf, "From:",
5242 strlen("From:")) == 0) {
5243 err |= (fputs(" (by way of ", fdest) == EOF);
5244 if (compose->account->name
5245 && *compose->account->name) {
5246 compose_convert_header
5247 (compose, buf, sizeof(buf),
5248 compose->account->name,
5251 err |= (fprintf(fdest, "%s <%s>",
5253 compose->account->address) < 0);
5255 err |= (fprintf(fdest, "%s",
5256 compose->account->address) < 0);
5257 err |= (fputs(")", fdest) == EOF);
5261 if (fputs("\n", fdest) == -1)
5268 if (compose_redirect_write_headers(compose, fdest))
5271 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5272 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5285 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5287 GtkTextBuffer *buffer;
5288 GtkTextIter start, end;
5291 const gchar *out_codeset;
5292 EncodingType encoding = ENC_UNKNOWN;
5293 MimeInfo *mimemsg, *mimetext;
5295 const gchar *src_codeset = CS_INTERNAL;
5296 gchar *from_addr = NULL;
5297 gchar *from_name = NULL;
5299 if (action == COMPOSE_WRITE_FOR_SEND)
5300 attach_parts = TRUE;
5302 /* create message MimeInfo */
5303 mimemsg = procmime_mimeinfo_new();
5304 mimemsg->type = MIMETYPE_MESSAGE;
5305 mimemsg->subtype = g_strdup("rfc822");
5306 mimemsg->content = MIMECONTENT_MEM;
5307 mimemsg->tmp = TRUE; /* must free content later */
5308 mimemsg->data.mem = compose_get_header(compose);
5310 /* Create text part MimeInfo */
5311 /* get all composed text */
5312 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5313 gtk_text_buffer_get_start_iter(buffer, &start);
5314 gtk_text_buffer_get_end_iter(buffer, &end);
5315 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5317 out_codeset = conv_get_charset_str(compose->out_encoding);
5319 if (!out_codeset && is_ascii_str(chars)) {
5320 out_codeset = CS_US_ASCII;
5321 } else if (prefs_common.outgoing_fallback_to_ascii &&
5322 is_ascii_str(chars)) {
5323 out_codeset = CS_US_ASCII;
5324 encoding = ENC_7BIT;
5328 gchar *test_conv_global_out = NULL;
5329 gchar *test_conv_reply = NULL;
5331 /* automatic mode. be automatic. */
5332 codeconv_set_strict(TRUE);
5334 out_codeset = conv_get_outgoing_charset_str();
5336 debug_print("trying to convert to %s\n", out_codeset);
5337 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5340 if (!test_conv_global_out && compose->orig_charset
5341 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5342 out_codeset = compose->orig_charset;
5343 debug_print("failure; trying to convert to %s\n", out_codeset);
5344 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5347 if (!test_conv_global_out && !test_conv_reply) {
5349 out_codeset = CS_INTERNAL;
5350 debug_print("failure; finally using %s\n", out_codeset);
5352 g_free(test_conv_global_out);
5353 g_free(test_conv_reply);
5354 codeconv_set_strict(FALSE);
5357 if (encoding == ENC_UNKNOWN) {
5358 if (prefs_common.encoding_method == CTE_BASE64)
5359 encoding = ENC_BASE64;
5360 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5361 encoding = ENC_QUOTED_PRINTABLE;
5362 else if (prefs_common.encoding_method == CTE_8BIT)
5363 encoding = ENC_8BIT;
5365 encoding = procmime_get_encoding_for_charset(out_codeset);
5368 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5369 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5371 if (action == COMPOSE_WRITE_FOR_SEND) {
5372 codeconv_set_strict(TRUE);
5373 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5374 codeconv_set_strict(FALSE);
5380 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5381 "to the specified %s charset.\n"
5382 "Send it as %s?"), out_codeset, src_codeset);
5383 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5384 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5387 if (aval != G_ALERTALTERNATE) {
5392 out_codeset = src_codeset;
5398 out_codeset = src_codeset;
5403 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5404 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5405 strstr(buf, "\nFrom ") != NULL) {
5406 encoding = ENC_QUOTED_PRINTABLE;
5410 mimetext = procmime_mimeinfo_new();
5411 mimetext->content = MIMECONTENT_MEM;
5412 mimetext->tmp = TRUE; /* must free content later */
5413 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5414 * and free the data, which we need later. */
5415 mimetext->data.mem = g_strdup(buf);
5416 mimetext->type = MIMETYPE_TEXT;
5417 mimetext->subtype = g_strdup("plain");
5418 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5419 g_strdup(out_codeset));
5421 /* protect trailing spaces when signing message */
5422 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5423 privacy_system_can_sign(compose->privacy_system)) {
5424 encoding = ENC_QUOTED_PRINTABLE;
5427 debug_print("main text: %zd bytes encoded as %s in %d\n",
5428 strlen(buf), out_codeset, encoding);
5430 /* check for line length limit */
5431 if (action == COMPOSE_WRITE_FOR_SEND &&
5432 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5433 check_line_length(buf, 1000, &line) < 0) {
5437 msg = g_strdup_printf
5438 (_("Line %d exceeds the line length limit (998 bytes).\n"
5439 "The contents of the message might be broken on the way to the delivery.\n"
5441 "Send it anyway?"), line + 1);
5442 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5444 if (aval != G_ALERTALTERNATE) {
5450 if (encoding != ENC_UNKNOWN)
5451 procmime_encode_content(mimetext, encoding);
5453 /* append attachment parts */
5454 if (compose_use_attach(compose) && attach_parts) {
5455 MimeInfo *mimempart;
5456 gchar *boundary = NULL;
5457 mimempart = procmime_mimeinfo_new();
5458 mimempart->content = MIMECONTENT_EMPTY;
5459 mimempart->type = MIMETYPE_MULTIPART;
5460 mimempart->subtype = g_strdup("mixed");
5464 boundary = generate_mime_boundary(NULL);
5465 } while (strstr(buf, boundary) != NULL);
5467 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5470 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5472 g_node_append(mimempart->node, mimetext->node);
5473 g_node_append(mimemsg->node, mimempart->node);
5475 if (compose_add_attachments(compose, mimempart) < 0)
5478 g_node_append(mimemsg->node, mimetext->node);
5482 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5483 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5484 /* extract name and address */
5485 if (strstr(spec, " <") && strstr(spec, ">")) {
5486 from_addr = g_strdup(strrchr(spec, '<')+1);
5487 *(strrchr(from_addr, '>')) = '\0';
5488 from_name = g_strdup(spec);
5489 *(strrchr(from_name, '<')) = '\0';
5496 /* sign message if sending */
5497 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5498 privacy_system_can_sign(compose->privacy_system))
5499 if (!privacy_sign(compose->privacy_system, mimemsg,
5500 compose->account, from_addr)) {
5507 procmime_write_mimeinfo(mimemsg, fp);
5509 procmime_mimeinfo_free_all(mimemsg);
5514 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5516 GtkTextBuffer *buffer;
5517 GtkTextIter start, end;
5522 if ((fp = g_fopen(file, "wb")) == NULL) {
5523 FILE_OP_ERROR(file, "fopen");
5527 /* chmod for security */
5528 if (change_file_mode_rw(fp, file) < 0) {
5529 FILE_OP_ERROR(file, "chmod");
5530 g_warning("can't change file mode\n");
5533 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5534 gtk_text_buffer_get_start_iter(buffer, &start);
5535 gtk_text_buffer_get_end_iter(buffer, &end);
5536 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5538 chars = conv_codeset_strdup
5539 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5542 if (!chars) return -1;
5545 len = strlen(chars);
5546 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5547 FILE_OP_ERROR(file, "fwrite");
5556 if (fclose(fp) == EOF) {
5557 FILE_OP_ERROR(file, "fclose");
5564 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5567 MsgInfo *msginfo = compose->targetinfo;
5569 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5570 if (!msginfo) return -1;
5572 if (!force && MSG_IS_LOCKED(msginfo->flags))
5575 item = msginfo->folder;
5576 cm_return_val_if_fail(item != NULL, -1);
5578 if (procmsg_msg_exist(msginfo) &&
5579 (folder_has_parent_of_type(item, F_QUEUE) ||
5580 folder_has_parent_of_type(item, F_DRAFT)
5581 || msginfo == compose->autosaved_draft)) {
5582 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5583 g_warning("can't remove the old message\n");
5586 debug_print("removed reedit target %d\n", msginfo->msgnum);
5593 static void compose_remove_draft(Compose *compose)
5596 MsgInfo *msginfo = compose->targetinfo;
5597 drafts = account_get_special_folder(compose->account, F_DRAFT);
5599 if (procmsg_msg_exist(msginfo)) {
5600 folder_item_remove_msg(drafts, msginfo->msgnum);
5605 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5606 gboolean remove_reedit_target)
5608 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5611 static gboolean compose_warn_encryption(Compose *compose)
5613 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5614 AlertValue val = G_ALERTALTERNATE;
5616 if (warning == NULL)
5619 val = alertpanel_full(_("Encryption warning"), warning,
5620 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5621 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5622 if (val & G_ALERTDISABLE) {
5623 val &= ~G_ALERTDISABLE;
5624 if (val == G_ALERTALTERNATE)
5625 privacy_inhibit_encrypt_warning(compose->privacy_system,
5629 if (val == G_ALERTALTERNATE) {
5636 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5637 gchar **msgpath, gboolean check_subject,
5638 gboolean remove_reedit_target)
5645 static gboolean lock = FALSE;
5646 PrefsAccount *mailac = NULL, *newsac = NULL;
5647 gboolean err = FALSE;
5649 debug_print("queueing message...\n");
5650 cm_return_val_if_fail(compose->account != NULL, -1);
5654 if (compose_check_entries(compose, check_subject) == FALSE) {
5656 if (compose->batch) {
5657 gtk_widget_show_all(compose->window);
5662 if (!compose->to_list && !compose->newsgroup_list) {
5663 g_warning("can't get recipient list.");
5668 if (compose->to_list) {
5669 if (compose->account->protocol != A_NNTP)
5670 mailac = compose->account;
5671 else if (cur_account && cur_account->protocol != A_NNTP)
5672 mailac = cur_account;
5673 else if (!(mailac = compose_current_mail_account())) {
5675 alertpanel_error(_("No account for sending mails available!"));
5680 if (compose->newsgroup_list) {
5681 if (compose->account->protocol == A_NNTP)
5682 newsac = compose->account;
5685 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5690 /* write queue header */
5691 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5692 G_DIR_SEPARATOR, compose, (guint) rand());
5693 debug_print("queuing to %s\n", tmp);
5694 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5695 FILE_OP_ERROR(tmp, "fopen");
5701 if (change_file_mode_rw(fp, tmp) < 0) {
5702 FILE_OP_ERROR(tmp, "chmod");
5703 g_warning("can't change file mode\n");
5706 /* queueing variables */
5707 err |= (fprintf(fp, "AF:\n") < 0);
5708 err |= (fprintf(fp, "NF:0\n") < 0);
5709 err |= (fprintf(fp, "PS:10\n") < 0);
5710 err |= (fprintf(fp, "SRH:1\n") < 0);
5711 err |= (fprintf(fp, "SFN:\n") < 0);
5712 err |= (fprintf(fp, "DSR:\n") < 0);
5714 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5716 err |= (fprintf(fp, "MID:\n") < 0);
5717 err |= (fprintf(fp, "CFG:\n") < 0);
5718 err |= (fprintf(fp, "PT:0\n") < 0);
5719 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5720 err |= (fprintf(fp, "RQ:\n") < 0);
5722 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5724 err |= (fprintf(fp, "SSV:\n") < 0);
5726 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5728 err |= (fprintf(fp, "NSV:\n") < 0);
5729 err |= (fprintf(fp, "SSH:\n") < 0);
5730 /* write recepient list */
5731 if (compose->to_list) {
5732 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5733 for (cur = compose->to_list->next; cur != NULL;
5735 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5736 err |= (fprintf(fp, "\n") < 0);
5738 /* write newsgroup list */
5739 if (compose->newsgroup_list) {
5740 err |= (fprintf(fp, "NG:") < 0);
5741 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5742 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5743 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5744 err |= (fprintf(fp, "\n") < 0);
5746 /* Sylpheed account IDs */
5748 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5750 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5753 if (compose->privacy_system != NULL) {
5754 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5755 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5756 if (compose->use_encryption) {
5758 if (!compose_warn_encryption(compose)) {
5765 if (mailac && mailac->encrypt_to_self) {
5766 GSList *tmp_list = g_slist_copy(compose->to_list);
5767 tmp_list = g_slist_append(tmp_list, compose->account->address);
5768 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5769 g_slist_free(tmp_list);
5771 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5773 if (encdata != NULL) {
5774 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5775 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5776 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5778 } /* else we finally dont want to encrypt */
5780 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5781 /* and if encdata was null, it means there's been a problem in
5793 /* Save copy folder */
5794 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5795 gchar *savefolderid;
5797 savefolderid = compose_get_save_to(compose);
5798 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5799 g_free(savefolderid);
5801 /* Save copy folder */
5802 if (compose->return_receipt) {
5803 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5805 /* Message-ID of message replying to */
5806 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5809 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5810 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5813 /* Message-ID of message forwarding to */
5814 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5817 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5818 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5822 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5823 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5825 /* end of headers */
5826 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5828 if (compose->redirect_filename != NULL) {
5829 if (compose_redirect_write_to_file(compose, fp) < 0) {
5838 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5843 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5847 g_warning("failed to write queue message\n");
5854 if (fclose(fp) == EOF) {
5855 FILE_OP_ERROR(tmp, "fclose");
5862 if (item && *item) {
5865 queue = account_get_special_folder(compose->account, F_QUEUE);
5868 g_warning("can't find queue folder\n");
5874 folder_item_scan(queue);
5875 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5876 g_warning("can't queue the message\n");
5883 if (msgpath == NULL) {
5889 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5890 compose_remove_reedit_target(compose, FALSE);
5893 if ((msgnum != NULL) && (item != NULL)) {
5901 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5904 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5906 struct stat statbuf;
5907 gchar *type, *subtype;
5908 GtkTreeModel *model;
5911 model = gtk_tree_view_get_model(tree_view);
5913 if (!gtk_tree_model_get_iter_first(model, &iter))
5916 gtk_tree_model_get(model, &iter,
5920 if (!is_file_exist(ainfo->file)) {
5921 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5922 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5923 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5925 if (val == G_ALERTDEFAULT) {
5930 mimepart = procmime_mimeinfo_new();
5931 mimepart->content = MIMECONTENT_FILE;
5932 mimepart->data.filename = g_strdup(ainfo->file);
5933 mimepart->tmp = FALSE; /* or we destroy our attachment */
5934 mimepart->offset = 0;
5936 g_stat(ainfo->file, &statbuf);
5937 mimepart->length = statbuf.st_size;
5939 type = g_strdup(ainfo->content_type);
5941 if (!strchr(type, '/')) {
5943 type = g_strdup("application/octet-stream");
5946 subtype = strchr(type, '/') + 1;
5947 *(subtype - 1) = '\0';
5948 mimepart->type = procmime_get_media_type(type);
5949 mimepart->subtype = g_strdup(subtype);
5952 if (mimepart->type == MIMETYPE_MESSAGE &&
5953 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5954 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5957 if (mimepart->type == MIMETYPE_APPLICATION &&
5958 !strcmp2(mimepart->subtype, "octet-stream"))
5959 g_hash_table_insert(mimepart->typeparameters,
5960 g_strdup("name"), g_strdup(ainfo->name));
5961 g_hash_table_insert(mimepart->dispositionparameters,
5962 g_strdup("filename"), g_strdup(ainfo->name));
5963 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5967 if (compose->use_signing) {
5968 if (ainfo->encoding == ENC_7BIT)
5969 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5970 else if (ainfo->encoding == ENC_8BIT)
5971 ainfo->encoding = ENC_BASE64;
5974 procmime_encode_content(mimepart, ainfo->encoding);
5976 g_node_append(parent->node, mimepart->node);
5977 } while (gtk_tree_model_iter_next(model, &iter));
5982 #define IS_IN_CUSTOM_HEADER(header) \
5983 (compose->account->add_customhdr && \
5984 custom_header_find(compose->account->customhdr_list, header) != NULL)
5986 static void compose_add_headerfield_from_headerlist(Compose *compose,
5988 const gchar *fieldname,
5989 const gchar *seperator)
5991 gchar *str, *fieldname_w_colon;
5992 gboolean add_field = FALSE;
5994 ComposeHeaderEntry *headerentry;
5995 const gchar *headerentryname;
5996 const gchar *trans_fieldname;
5999 if (IS_IN_CUSTOM_HEADER(fieldname))
6002 debug_print("Adding %s-fields\n", fieldname);
6004 fieldstr = g_string_sized_new(64);
6006 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6007 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6009 for (list = compose->header_list; list; list = list->next) {
6010 headerentry = ((ComposeHeaderEntry *)list->data);
6011 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6013 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6014 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6016 if (str[0] != '\0') {
6018 g_string_append(fieldstr, seperator);
6019 g_string_append(fieldstr, str);
6028 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6029 compose_convert_header
6030 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6031 strlen(fieldname) + 2, TRUE);
6032 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6036 g_free(fieldname_w_colon);
6037 g_string_free(fieldstr, TRUE);
6042 static gchar *compose_get_header(Compose *compose)
6044 gchar buf[BUFFSIZE];
6045 const gchar *entry_str;
6049 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6051 gchar *from_name = NULL, *from_address = NULL;
6054 cm_return_val_if_fail(compose->account != NULL, NULL);
6055 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6057 header = g_string_sized_new(64);
6060 get_rfc822_date(buf, sizeof(buf));
6061 g_string_append_printf(header, "Date: %s\n", buf);
6065 if (compose->account->name && *compose->account->name) {
6067 QUOTE_IF_REQUIRED(buf, compose->account->name);
6068 tmp = g_strdup_printf("%s <%s>",
6069 buf, compose->account->address);
6071 tmp = g_strdup_printf("%s",
6072 compose->account->address);
6074 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6075 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6077 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6078 from_address = g_strdup(compose->account->address);
6080 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6081 /* extract name and address */
6082 if (strstr(spec, " <") && strstr(spec, ">")) {
6083 from_address = g_strdup(strrchr(spec, '<')+1);
6084 *(strrchr(from_address, '>')) = '\0';
6085 from_name = g_strdup(spec);
6086 *(strrchr(from_name, '<')) = '\0';
6089 from_address = g_strdup(spec);
6096 if (from_name && *from_name) {
6097 compose_convert_header
6098 (compose, buf, sizeof(buf), from_name,
6099 strlen("From: "), TRUE);
6100 QUOTE_IF_REQUIRED(name, buf);
6102 g_string_append_printf(header, "From: %s <%s>\n",
6103 name, from_address);
6105 g_string_append_printf(header, "From: %s\n", from_address);
6108 g_free(from_address);
6111 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6114 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6117 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6121 * If this account is a NNTP account remove Bcc header from
6122 * message body since it otherwise will be publicly shown
6124 if (compose->account->protocol != A_NNTP)
6125 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6128 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6130 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6133 compose_convert_header(compose, buf, sizeof(buf), str,
6134 strlen("Subject: "), FALSE);
6135 g_string_append_printf(header, "Subject: %s\n", buf);
6141 if (compose->account->set_domain && compose->account->domain) {
6142 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6143 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6144 g_snprintf(buf, sizeof(buf), "%s",
6145 strchr(compose->account->address, '@') ?
6146 strchr(compose->account->address, '@')+1 :
6147 compose->account->address);
6149 g_snprintf(buf, sizeof(buf), "%s", "");
6152 if (compose->account->gen_msgid) {
6154 if (compose->account->msgid_with_addr) {
6155 addr = compose->account->address;
6157 generate_msgid(buf, sizeof(buf), addr);
6158 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6159 compose->msgid = g_strdup(buf);
6161 compose->msgid = NULL;
6164 if (compose->remove_references == FALSE) {
6166 if (compose->inreplyto && compose->to_list)
6167 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6170 if (compose->references)
6171 g_string_append_printf(header, "References: %s\n", compose->references);
6175 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6178 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6181 if (compose->account->organization &&
6182 strlen(compose->account->organization) &&
6183 !IS_IN_CUSTOM_HEADER("Organization")) {
6184 compose_convert_header(compose, buf, sizeof(buf),
6185 compose->account->organization,
6186 strlen("Organization: "), FALSE);
6187 g_string_append_printf(header, "Organization: %s\n", buf);
6190 /* Program version and system info */
6191 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6192 !compose->newsgroup_list) {
6193 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6195 gtk_major_version, gtk_minor_version, gtk_micro_version,
6198 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6199 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6201 gtk_major_version, gtk_minor_version, gtk_micro_version,
6205 /* custom headers */
6206 if (compose->account->add_customhdr) {
6209 for (cur = compose->account->customhdr_list; cur != NULL;
6211 CustomHeader *chdr = (CustomHeader *)cur->data;
6213 if (custom_header_is_allowed(chdr->name)) {
6214 compose_convert_header
6215 (compose, buf, sizeof(buf),
6216 chdr->value ? chdr->value : "",
6217 strlen(chdr->name) + 2, FALSE);
6218 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6223 /* Automatic Faces and X-Faces */
6224 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6225 g_string_append_printf(header, "X-Face: %s\n", buf);
6227 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6228 g_string_append_printf(header, "X-Face: %s\n", buf);
6230 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6231 g_string_append_printf(header, "Face: %s\n", buf);
6233 else if (get_default_face (buf, sizeof(buf)) == 0) {
6234 g_string_append_printf(header, "Face: %s\n", buf);
6238 switch (compose->priority) {
6239 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6240 "X-Priority: 1 (Highest)\n");
6242 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6243 "X-Priority: 2 (High)\n");
6245 case PRIORITY_NORMAL: break;
6246 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6247 "X-Priority: 4 (Low)\n");
6249 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6250 "X-Priority: 5 (Lowest)\n");
6252 default: debug_print("compose: priority unknown : %d\n",
6256 /* Request Return Receipt */
6257 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6258 if (compose->return_receipt) {
6259 if (compose->account->name
6260 && *compose->account->name) {
6261 compose_convert_header(compose, buf, sizeof(buf),
6262 compose->account->name,
6263 strlen("Disposition-Notification-To: "),
6265 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6267 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6271 /* get special headers */
6272 for (list = compose->header_list; list; list = list->next) {
6273 ComposeHeaderEntry *headerentry;
6276 gchar *headername_wcolon;
6277 const gchar *headername_trans;
6280 gboolean standard_header = FALSE;
6282 headerentry = ((ComposeHeaderEntry *)list->data);
6284 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6286 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6291 if (!strstr(tmp, ":")) {
6292 headername_wcolon = g_strconcat(tmp, ":", NULL);
6293 headername = g_strdup(tmp);
6295 headername_wcolon = g_strdup(tmp);
6296 headername = g_strdup(strtok(tmp, ":"));
6300 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6301 Xstrdup_a(headervalue, entry_str, return NULL);
6302 subst_char(headervalue, '\r', ' ');
6303 subst_char(headervalue, '\n', ' ');
6304 string = std_headers;
6305 while (*string != NULL) {
6306 headername_trans = prefs_common_translated_header_name(*string);
6307 if (!strcmp(headername_trans, headername_wcolon))
6308 standard_header = TRUE;
6311 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6312 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6315 g_free(headername_wcolon);
6319 g_string_free(header, FALSE);
6324 #undef IS_IN_CUSTOM_HEADER
6326 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6327 gint header_len, gboolean addr_field)
6329 gchar *tmpstr = NULL;
6330 const gchar *out_codeset = NULL;
6332 cm_return_if_fail(src != NULL);
6333 cm_return_if_fail(dest != NULL);
6335 if (len < 1) return;
6337 tmpstr = g_strdup(src);
6339 subst_char(tmpstr, '\n', ' ');
6340 subst_char(tmpstr, '\r', ' ');
6343 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6344 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6345 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6350 codeconv_set_strict(TRUE);
6351 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6352 conv_get_charset_str(compose->out_encoding));
6353 codeconv_set_strict(FALSE);
6355 if (!dest || *dest == '\0') {
6356 gchar *test_conv_global_out = NULL;
6357 gchar *test_conv_reply = NULL;
6359 /* automatic mode. be automatic. */
6360 codeconv_set_strict(TRUE);
6362 out_codeset = conv_get_outgoing_charset_str();
6364 debug_print("trying to convert to %s\n", out_codeset);
6365 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6368 if (!test_conv_global_out && compose->orig_charset
6369 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6370 out_codeset = compose->orig_charset;
6371 debug_print("failure; trying to convert to %s\n", out_codeset);
6372 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6375 if (!test_conv_global_out && !test_conv_reply) {
6377 out_codeset = CS_INTERNAL;
6378 debug_print("finally using %s\n", out_codeset);
6380 g_free(test_conv_global_out);
6381 g_free(test_conv_reply);
6382 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6384 codeconv_set_strict(FALSE);
6389 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6393 cm_return_if_fail(user_data != NULL);
6395 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6396 g_strstrip(address);
6397 if (*address != '\0') {
6398 gchar *name = procheader_get_fromname(address);
6399 extract_address(address);
6400 addressbook_add_contact(name, address, NULL, NULL);
6405 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6407 GtkWidget *menuitem;
6410 cm_return_if_fail(menu != NULL);
6411 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6413 menuitem = gtk_separator_menu_item_new();
6414 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6415 gtk_widget_show(menuitem);
6417 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6418 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6420 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6421 g_strstrip(address);
6422 if (*address == '\0') {
6423 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6426 g_signal_connect(G_OBJECT(menuitem), "activate",
6427 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6428 gtk_widget_show(menuitem);
6431 static void compose_create_header_entry(Compose *compose)
6433 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6440 const gchar *header = NULL;
6441 ComposeHeaderEntry *headerentry;
6442 gboolean standard_header = FALSE;
6443 GtkListStore *model;
6445 #if !(GTK_CHECK_VERSION(2,12,0))
6446 GtkTooltips *tips = compose->tooltips;
6449 headerentry = g_new0(ComposeHeaderEntry, 1);
6452 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6453 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6454 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6456 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6458 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6460 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6461 COMPOSE_NEWSGROUPS);
6462 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6464 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6465 COMPOSE_FOLLOWUPTO);
6467 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6468 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6469 G_CALLBACK(compose_grab_focus_cb), compose);
6470 gtk_widget_show(combo);
6471 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6472 compose->header_nextrow, compose->header_nextrow+1,
6473 GTK_SHRINK, GTK_FILL, 0, 0);
6474 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6475 const gchar *last_header_entry = gtk_entry_get_text(
6476 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6478 while (*string != NULL) {
6479 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6480 standard_header = TRUE;
6483 if (standard_header)
6484 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6486 if (!compose->header_last || !standard_header) {
6487 switch(compose->account->protocol) {
6489 header = prefs_common_translated_header_name("Newsgroups:");
6492 header = prefs_common_translated_header_name("To:");
6497 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6499 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6500 G_CALLBACK(compose_grab_focus_cb), compose);
6502 /* Entry field with cleanup button */
6503 #if GTK_CHECK_VERSION(2, 8, 0)
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));
6508 button = gtk_button_new_with_label(_("Clear"));
6510 gtk_widget_show(button);
6511 CLAWS_SET_TIP(button,
6512 _("Delete entry contents"));
6513 entry = gtk_entry_new();
6514 gtk_widget_show(entry);
6515 CLAWS_SET_TIP(entry,
6516 _("Use <tab> to autocomplete from addressbook"));
6517 hbox = gtk_hbox_new (FALSE, 0);
6518 gtk_widget_show(hbox);
6519 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6520 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6521 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6522 compose->header_nextrow, compose->header_nextrow+1,
6523 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6525 g_signal_connect(G_OBJECT(entry), "key-press-event",
6526 G_CALLBACK(compose_headerentry_key_press_event_cb),
6528 g_signal_connect(G_OBJECT(entry), "changed",
6529 G_CALLBACK(compose_headerentry_changed_cb),
6531 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6532 G_CALLBACK(compose_grab_focus_cb), compose);
6534 g_signal_connect(G_OBJECT(button), "clicked",
6535 G_CALLBACK(compose_headerentry_button_clicked_cb),
6539 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6540 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6541 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6542 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6543 G_CALLBACK(compose_header_drag_received_cb),
6545 g_signal_connect(G_OBJECT(entry), "drag-drop",
6546 G_CALLBACK(compose_drag_drop),
6548 g_signal_connect(G_OBJECT(entry), "populate-popup",
6549 G_CALLBACK(compose_entry_popup_extend),
6552 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6554 headerentry->compose = compose;
6555 headerentry->combo = combo;
6556 headerentry->entry = entry;
6557 headerentry->button = button;
6558 headerentry->hbox = hbox;
6559 headerentry->headernum = compose->header_nextrow;
6560 headerentry->type = PREF_NONE;
6562 compose->header_nextrow++;
6563 compose->header_last = headerentry;
6564 compose->header_list =
6565 g_slist_append(compose->header_list,
6569 static void compose_add_header_entry(Compose *compose, const gchar *header,
6570 gchar *text, ComposePrefType pref_type)
6572 ComposeHeaderEntry *last_header = compose->header_last;
6573 gchar *tmp = g_strdup(text), *email;
6574 gboolean replyto_hdr = g_str_has_suffix(header, "-To:");
6576 extract_address(tmp);
6577 email = g_utf8_strdown(tmp, -1);
6579 if (replyto_hdr == FALSE &&
6580 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6582 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6583 header, text, (gint) pref_type);
6589 if (!strcmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6590 gtk_entry_set_text(GTK_ENTRY(
6591 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6593 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6594 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6595 last_header->type = pref_type;
6597 if (replyto_hdr == FALSE)
6598 g_hash_table_insert(compose->email_hashtable, email,
6599 GUINT_TO_POINTER(1));
6606 static void compose_destroy_headerentry(Compose *compose,
6607 ComposeHeaderEntry *headerentry)
6609 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6612 extract_address(text);
6613 email = g_utf8_strdown(text, -1);
6614 g_hash_table_remove(compose->email_hashtable, email);
6618 gtk_widget_destroy(headerentry->combo);
6619 gtk_widget_destroy(headerentry->entry);
6620 gtk_widget_destroy(headerentry->button);
6621 gtk_widget_destroy(headerentry->hbox);
6622 g_free(headerentry);
6625 static void compose_remove_header_entries(Compose *compose)
6628 for (list = compose->header_list; list; list = list->next)
6629 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6631 compose->header_last = NULL;
6632 g_slist_free(compose->header_list);
6633 compose->header_list = NULL;
6634 compose->header_nextrow = 1;
6635 compose_create_header_entry(compose);
6638 static GtkWidget *compose_create_header(Compose *compose)
6640 GtkWidget *from_optmenu_hbox;
6641 GtkWidget *header_scrolledwin;
6642 GtkWidget *header_table;
6646 /* header labels and entries */
6647 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6648 gtk_widget_show(header_scrolledwin);
6649 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6651 header_table = gtk_table_new(2, 2, FALSE);
6652 gtk_widget_show(header_table);
6653 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6654 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6655 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6658 /* option menu for selecting accounts */
6659 from_optmenu_hbox = compose_account_option_menu_create(compose);
6660 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6661 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6664 compose->header_table = header_table;
6665 compose->header_list = NULL;
6666 compose->header_nextrow = count;
6668 compose_create_header_entry(compose);
6670 compose->table = NULL;
6672 return header_scrolledwin ;
6675 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6677 Compose *compose = (Compose *)data;
6678 GdkEventButton event;
6681 event.time = gtk_get_current_event_time();
6683 return attach_button_pressed(compose->attach_clist, &event, compose);
6686 static GtkWidget *compose_create_attach(Compose *compose)
6688 GtkWidget *attach_scrwin;
6689 GtkWidget *attach_clist;
6691 GtkListStore *store;
6692 GtkCellRenderer *renderer;
6693 GtkTreeViewColumn *column;
6694 GtkTreeSelection *selection;
6696 /* attachment list */
6697 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6698 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6699 GTK_POLICY_AUTOMATIC,
6700 GTK_POLICY_AUTOMATIC);
6701 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6703 store = gtk_list_store_new(N_ATTACH_COLS,
6708 G_TYPE_AUTO_POINTER,
6710 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6711 (GTK_TREE_MODEL(store)));
6712 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6713 g_object_unref(store);
6715 renderer = gtk_cell_renderer_text_new();
6716 column = gtk_tree_view_column_new_with_attributes
6717 (_("Mime type"), renderer, "text",
6718 COL_MIMETYPE, NULL);
6719 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6721 renderer = gtk_cell_renderer_text_new();
6722 column = gtk_tree_view_column_new_with_attributes
6723 (_("Size"), renderer, "text",
6725 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6727 renderer = gtk_cell_renderer_text_new();
6728 column = gtk_tree_view_column_new_with_attributes
6729 (_("Name"), renderer, "text",
6731 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6733 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6734 prefs_common.use_stripes_everywhere);
6735 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6736 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6738 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6739 G_CALLBACK(attach_selected), compose);
6740 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6741 G_CALLBACK(attach_button_pressed), compose);
6743 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6744 G_CALLBACK(popup_attach_button_pressed), compose);
6746 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6747 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6748 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6749 G_CALLBACK(popup_attach_button_pressed), compose);
6751 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6752 G_CALLBACK(attach_key_pressed), compose);
6755 gtk_drag_dest_set(attach_clist,
6756 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6757 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6758 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6759 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6760 G_CALLBACK(compose_attach_drag_received_cb),
6762 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6763 G_CALLBACK(compose_drag_drop),
6766 compose->attach_scrwin = attach_scrwin;
6767 compose->attach_clist = attach_clist;
6769 return attach_scrwin;
6772 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6773 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6775 static GtkWidget *compose_create_others(Compose *compose)
6778 GtkWidget *savemsg_checkbtn;
6779 GtkWidget *savemsg_combo;
6780 GtkWidget *savemsg_select;
6783 gchar *folderidentifier;
6785 /* Table for settings */
6786 table = gtk_table_new(3, 1, FALSE);
6787 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6788 gtk_widget_show(table);
6789 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6792 /* Save Message to folder */
6793 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6794 gtk_widget_show(savemsg_checkbtn);
6795 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6796 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6797 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6799 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6800 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6802 savemsg_combo = gtk_combo_box_entry_new_text();
6803 compose->savemsg_checkbtn = savemsg_checkbtn;
6804 compose->savemsg_combo = savemsg_combo;
6805 gtk_widget_show(savemsg_combo);
6807 if (prefs_common.compose_save_to_history)
6808 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6809 prefs_common.compose_save_to_history);
6811 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6812 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6813 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6814 G_CALLBACK(compose_grab_focus_cb), compose);
6815 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6816 folderidentifier = folder_item_get_identifier(account_get_special_folder
6817 (compose->account, F_OUTBOX));
6818 compose_set_save_to(compose, folderidentifier);
6819 g_free(folderidentifier);
6822 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6823 gtk_widget_show(savemsg_select);
6824 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6825 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6826 G_CALLBACK(compose_savemsg_select_cb),
6834 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6836 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6837 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6840 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6845 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6848 path = folder_item_get_identifier(dest);
6850 compose_set_save_to(compose, path);
6854 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6855 GdkAtom clip, GtkTextIter *insert_place);
6858 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6862 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6864 if (event->button == 3) {
6866 GtkTextIter sel_start, sel_end;
6867 gboolean stuff_selected;
6869 /* move the cursor to allow GtkAspell to check the word
6870 * under the mouse */
6871 if (event->x && event->y) {
6872 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6873 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6875 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6878 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6879 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6882 stuff_selected = gtk_text_buffer_get_selection_bounds(
6884 &sel_start, &sel_end);
6886 gtk_text_buffer_place_cursor (buffer, &iter);
6887 /* reselect stuff */
6889 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6890 gtk_text_buffer_select_range(buffer,
6891 &sel_start, &sel_end);
6893 return FALSE; /* pass the event so that the right-click goes through */
6896 if (event->button == 2) {
6901 /* get the middle-click position to paste at the correct place */
6902 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6903 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6905 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6908 entry_paste_clipboard(compose, text,
6909 prefs_common.linewrap_pastes,
6910 GDK_SELECTION_PRIMARY, &iter);
6918 static void compose_spell_menu_changed(void *data)
6920 Compose *compose = (Compose *)data;
6922 GtkWidget *menuitem;
6923 GtkWidget *parent_item;
6924 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6927 if (compose->gtkaspell == NULL)
6930 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6931 "/Menu/Spelling/Options");
6933 /* setting the submenu removes /Spelling/Options from the factory
6934 * so we need to save it */
6936 if (parent_item == NULL) {
6937 parent_item = compose->aspell_options_menu;
6938 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6940 compose->aspell_options_menu = parent_item;
6942 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6944 spell_menu = g_slist_reverse(spell_menu);
6945 for (items = spell_menu;
6946 items; items = items->next) {
6947 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6948 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6949 gtk_widget_show(GTK_WIDGET(menuitem));
6951 g_slist_free(spell_menu);
6953 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6954 gtk_widget_show(parent_item);
6957 static void compose_dict_changed(void *data)
6959 Compose *compose = (Compose *) data;
6961 if(compose->gtkaspell &&
6962 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6965 gtkaspell_highlight_all(compose->gtkaspell);
6966 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6970 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6972 Compose *compose = (Compose *)data;
6973 GdkEventButton event;
6976 event.time = gtk_get_current_event_time();
6980 return text_clicked(compose->text, &event, compose);
6983 static gboolean compose_force_window_origin = TRUE;
6984 static Compose *compose_create(PrefsAccount *account,
6993 GtkWidget *handlebox;
6995 GtkWidget *notebook;
6997 GtkWidget *attach_hbox;
6998 GtkWidget *attach_lab1;
6999 GtkWidget *attach_lab2;
7004 GtkWidget *subject_hbox;
7005 GtkWidget *subject_frame;
7006 GtkWidget *subject_entry;
7010 GtkWidget *edit_vbox;
7011 GtkWidget *ruler_hbox;
7013 GtkWidget *scrolledwin;
7015 GtkTextBuffer *buffer;
7016 GtkClipboard *clipboard;
7019 UndoMain *undostruct;
7021 gchar *titles[N_ATTACH_COLS];
7022 GtkWidget *popupmenu;
7023 GtkWidget *tmpl_menu;
7024 GtkActionGroup *action_group = NULL;
7027 GtkAspell * gtkaspell = NULL;
7030 static GdkGeometry geometry;
7032 cm_return_val_if_fail(account != NULL, NULL);
7034 debug_print("Creating compose window...\n");
7035 compose = g_new0(Compose, 1);
7037 titles[COL_MIMETYPE] = _("MIME type");
7038 titles[COL_SIZE] = _("Size");
7039 titles[COL_NAME] = _("Name");
7041 compose->batch = batch;
7042 compose->account = account;
7043 compose->folder = folder;
7045 compose->mutex = g_mutex_new();
7046 compose->set_cursor_pos = -1;
7048 #if !(GTK_CHECK_VERSION(2,12,0))
7049 compose->tooltips = tips;
7052 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7054 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7055 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7057 if (!geometry.max_width) {
7058 geometry.max_width = gdk_screen_width();
7059 geometry.max_height = gdk_screen_height();
7062 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7063 &geometry, GDK_HINT_MAX_SIZE);
7064 if (!geometry.min_width) {
7065 geometry.min_width = 600;
7066 geometry.min_height = 440;
7068 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7069 &geometry, GDK_HINT_MIN_SIZE);
7071 #ifndef GENERIC_UMPC
7072 if (compose_force_window_origin)
7073 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7074 prefs_common.compose_y);
7076 g_signal_connect(G_OBJECT(window), "delete_event",
7077 G_CALLBACK(compose_delete_cb), compose);
7078 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7079 gtk_widget_realize(window);
7081 gtkut_widget_set_composer_icon(window);
7083 vbox = gtk_vbox_new(FALSE, 0);
7084 gtk_container_add(GTK_CONTAINER(window), vbox);
7086 compose->ui_manager = gtk_ui_manager_new();
7087 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7088 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7089 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7090 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7091 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7092 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7093 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7094 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7095 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7096 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7099 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7105 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7107 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7110 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7111 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7116 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7146 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7152 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7159 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7173 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7182 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7212 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7213 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7216 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)
7217 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)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7223 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)
7224 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)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7228 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7229 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)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7233 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)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7239 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)
7240 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7241 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7245 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)
7246 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)
7247 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7248 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7254 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7257 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7258 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)
7260 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7261 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7262 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7266 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7267 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7268 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7269 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7270 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7271 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7274 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7276 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7277 gtk_widget_show_all(menubar);
7279 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7281 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7283 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7286 if (prefs_common.toolbar_detachable) {
7287 handlebox = gtk_handle_box_new();
7289 handlebox = gtk_hbox_new(FALSE, 0);
7291 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7293 gtk_widget_realize(handlebox);
7295 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7298 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7302 vbox2 = gtk_vbox_new(FALSE, 2);
7303 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7304 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7307 notebook = gtk_notebook_new();
7308 gtk_widget_set_size_request(notebook, -1, 130);
7309 gtk_widget_show(notebook);
7311 /* header labels and entries */
7312 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7313 compose_create_header(compose),
7314 gtk_label_new_with_mnemonic(_("Hea_der")));
7315 /* attachment list */
7316 attach_hbox = gtk_hbox_new(FALSE, 0);
7317 gtk_widget_show(attach_hbox);
7319 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7320 gtk_widget_show(attach_lab1);
7321 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7323 attach_lab2 = gtk_label_new("");
7324 gtk_widget_show(attach_lab2);
7325 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7327 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7328 compose_create_attach(compose),
7331 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7332 compose_create_others(compose),
7333 gtk_label_new_with_mnemonic(_("Othe_rs")));
7336 subject_hbox = gtk_hbox_new(FALSE, 0);
7337 gtk_widget_show(subject_hbox);
7339 subject_frame = gtk_frame_new(NULL);
7340 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7341 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7342 gtk_widget_show(subject_frame);
7344 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7345 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7346 gtk_widget_show(subject);
7348 label = gtk_label_new(_("Subject:"));
7349 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7350 gtk_widget_show(label);
7353 subject_entry = claws_spell_entry_new();
7355 subject_entry = gtk_entry_new();
7357 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7358 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7359 G_CALLBACK(compose_grab_focus_cb), compose);
7360 gtk_widget_show(subject_entry);
7361 compose->subject_entry = subject_entry;
7362 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7364 edit_vbox = gtk_vbox_new(FALSE, 0);
7366 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7369 ruler_hbox = gtk_hbox_new(FALSE, 0);
7370 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7372 ruler = gtk_shruler_new();
7373 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7374 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7378 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7379 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7380 GTK_POLICY_AUTOMATIC,
7381 GTK_POLICY_AUTOMATIC);
7382 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7384 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7385 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7387 text = gtk_text_view_new();
7388 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7389 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7390 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7391 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7392 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7394 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7396 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7397 G_CALLBACK(compose_edit_size_alloc),
7399 g_signal_connect(G_OBJECT(buffer), "changed",
7400 G_CALLBACK(compose_changed_cb), compose);
7401 g_signal_connect(G_OBJECT(text), "grab_focus",
7402 G_CALLBACK(compose_grab_focus_cb), compose);
7403 g_signal_connect(G_OBJECT(buffer), "insert_text",
7404 G_CALLBACK(text_inserted), compose);
7405 g_signal_connect(G_OBJECT(text), "button_press_event",
7406 G_CALLBACK(text_clicked), compose);
7408 g_signal_connect(G_OBJECT(text), "popup-menu",
7409 G_CALLBACK(compose_popup_menu), compose);
7411 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7412 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7413 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7414 G_CALLBACK(compose_popup_menu), compose);
7416 g_signal_connect(G_OBJECT(subject_entry), "changed",
7417 G_CALLBACK(compose_changed_cb), compose);
7420 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7421 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7422 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7423 g_signal_connect(G_OBJECT(text), "drag_data_received",
7424 G_CALLBACK(compose_insert_drag_received_cb),
7426 g_signal_connect(G_OBJECT(text), "drag-drop",
7427 G_CALLBACK(compose_drag_drop),
7429 gtk_widget_show_all(vbox);
7431 /* pane between attach clist and text */
7432 paned = gtk_vpaned_new();
7433 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7435 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7436 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7438 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7440 gtk_paned_add1(GTK_PANED(paned), notebook);
7441 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7442 gtk_widget_show_all(paned);
7445 if (prefs_common.textfont) {
7446 PangoFontDescription *font_desc;
7448 font_desc = pango_font_description_from_string
7449 (prefs_common.textfont);
7451 gtk_widget_modify_font(text, font_desc);
7452 pango_font_description_free(font_desc);
7456 gtk_action_group_add_actions(action_group, compose_popup_entries,
7457 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7459 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7461 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7465 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7471 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7473 undostruct = undo_init(text);
7474 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7477 address_completion_start(window);
7479 compose->window = window;
7480 compose->vbox = vbox;
7481 compose->menubar = menubar;
7482 compose->handlebox = handlebox;
7484 compose->vbox2 = vbox2;
7486 compose->paned = paned;
7488 compose->attach_label = attach_lab2;
7490 compose->notebook = notebook;
7491 compose->edit_vbox = edit_vbox;
7492 compose->ruler_hbox = ruler_hbox;
7493 compose->ruler = ruler;
7494 compose->scrolledwin = scrolledwin;
7495 compose->text = text;
7497 compose->focused_editable = NULL;
7499 compose->popupmenu = popupmenu;
7501 compose->tmpl_menu = tmpl_menu;
7503 compose->mode = mode;
7504 compose->rmode = mode;
7506 compose->targetinfo = NULL;
7507 compose->replyinfo = NULL;
7508 compose->fwdinfo = NULL;
7510 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7511 g_str_equal, (GDestroyNotify) g_free, NULL);
7513 compose->replyto = NULL;
7515 compose->bcc = NULL;
7516 compose->followup_to = NULL;
7518 compose->ml_post = NULL;
7520 compose->inreplyto = NULL;
7521 compose->references = NULL;
7522 compose->msgid = NULL;
7523 compose->boundary = NULL;
7525 compose->autowrap = prefs_common.autowrap;
7526 compose->autoindent = prefs_common.auto_indent;
7527 compose->use_signing = FALSE;
7528 compose->use_encryption = FALSE;
7529 compose->privacy_system = NULL;
7531 compose->modified = FALSE;
7533 compose->return_receipt = FALSE;
7535 compose->to_list = NULL;
7536 compose->newsgroup_list = NULL;
7538 compose->undostruct = undostruct;
7540 compose->sig_str = NULL;
7542 compose->exteditor_file = NULL;
7543 compose->exteditor_pid = -1;
7544 compose->exteditor_tag = -1;
7545 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7549 if (mode != COMPOSE_REDIRECT) {
7550 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7551 strcmp(prefs_common.dictionary, "")) {
7552 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7553 prefs_common.alt_dictionary,
7554 conv_get_locale_charset_str(),
7555 prefs_common.misspelled_col,
7556 prefs_common.check_while_typing,
7557 prefs_common.recheck_when_changing_dict,
7558 prefs_common.use_alternate,
7559 prefs_common.use_both_dicts,
7560 GTK_TEXT_VIEW(text),
7561 GTK_WINDOW(compose->window),
7562 compose_dict_changed,
7563 compose_spell_menu_changed,
7566 alertpanel_error(_("Spell checker could not "
7568 gtkaspell_checkers_strerror());
7569 gtkaspell_checkers_reset_error();
7571 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7575 compose->gtkaspell = gtkaspell;
7576 compose_spell_menu_changed(compose);
7577 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7580 compose_select_account(compose, account, TRUE);
7582 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7583 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7585 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7586 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7588 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7589 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7591 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7592 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7594 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7595 if (account->protocol != A_NNTP)
7596 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7597 prefs_common_translated_header_name("To:"));
7599 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7600 prefs_common_translated_header_name("Newsgroups:"));
7602 addressbook_set_target_compose(compose);
7604 if (mode != COMPOSE_REDIRECT)
7605 compose_set_template_menu(compose);
7607 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7610 compose_list = g_list_append(compose_list, compose);
7612 if (!prefs_common.show_ruler)
7613 gtk_widget_hide(ruler_hbox);
7615 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7618 compose->priority = PRIORITY_NORMAL;
7619 compose_update_priority_menu_item(compose);
7621 compose_set_out_encoding(compose);
7624 compose_update_actions_menu(compose);
7626 /* Privacy Systems menu */
7627 compose_update_privacy_systems_menu(compose);
7629 activate_privacy_system(compose, account, TRUE);
7630 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7632 gtk_widget_realize(window);
7634 gtk_widget_show(window);
7636 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7637 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7644 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7649 GtkWidget *optmenubox;
7652 GtkWidget *from_name = NULL;
7653 #if !(GTK_CHECK_VERSION(2,12,0))
7654 GtkTooltips *tips = compose->tooltips;
7657 gint num = 0, def_menu = 0;
7659 accounts = account_get_list();
7660 cm_return_val_if_fail(accounts != NULL, NULL);
7662 optmenubox = gtk_event_box_new();
7663 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7664 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7666 hbox = gtk_hbox_new(FALSE, 6);
7667 from_name = gtk_entry_new();
7669 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7670 G_CALLBACK(compose_grab_focus_cb), compose);
7672 for (; accounts != NULL; accounts = accounts->next, num++) {
7673 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7674 gchar *name, *from = NULL;
7676 if (ac == compose->account) def_menu = num;
7678 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7681 if (ac == compose->account) {
7682 if (ac->name && *ac->name) {
7684 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7685 from = g_strdup_printf("%s <%s>",
7687 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7689 from = g_strdup_printf("%s",
7691 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7694 COMBOBOX_ADD(menu, name, ac->account_id);
7699 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7701 g_signal_connect(G_OBJECT(optmenu), "changed",
7702 G_CALLBACK(account_activated),
7704 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7705 G_CALLBACK(compose_entry_popup_extend),
7708 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7709 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7711 CLAWS_SET_TIP(optmenubox,
7712 _("Account to use for this email"));
7713 CLAWS_SET_TIP(from_name,
7714 _("Sender address to be used"));
7716 compose->from_name = from_name;
7721 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7723 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7724 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7725 Compose *compose = (Compose *) data;
7727 compose->priority = value;
7731 static void compose_reply_change_mode(Compose *compose,
7734 gboolean was_modified = compose->modified;
7736 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7738 cm_return_if_fail(compose->replyinfo != NULL);
7740 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7742 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7744 if (action == COMPOSE_REPLY_TO_ALL)
7746 if (action == COMPOSE_REPLY_TO_SENDER)
7748 if (action == COMPOSE_REPLY_TO_LIST)
7751 compose_remove_header_entries(compose);
7752 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7753 if (compose->account->set_autocc && compose->account->auto_cc)
7754 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7756 if (compose->account->set_autobcc && compose->account->auto_bcc)
7757 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7759 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7760 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7761 compose_show_first_last_header(compose, TRUE);
7762 compose->modified = was_modified;
7763 compose_set_title(compose);
7766 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7768 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7769 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7770 Compose *compose = (Compose *) data;
7773 compose_reply_change_mode(compose, value);
7776 static void compose_update_priority_menu_item(Compose * compose)
7778 GtkWidget *menuitem = NULL;
7779 switch (compose->priority) {
7780 case PRIORITY_HIGHEST:
7781 menuitem = gtk_ui_manager_get_widget
7782 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7785 menuitem = gtk_ui_manager_get_widget
7786 (compose->ui_manager, "/Menu/Options/Priority/High");
7788 case PRIORITY_NORMAL:
7789 menuitem = gtk_ui_manager_get_widget
7790 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7793 menuitem = gtk_ui_manager_get_widget
7794 (compose->ui_manager, "/Menu/Options/Priority/Low");
7796 case PRIORITY_LOWEST:
7797 menuitem = gtk_ui_manager_get_widget
7798 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7801 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7804 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7806 Compose *compose = (Compose *) data;
7808 gboolean can_sign = FALSE, can_encrypt = FALSE;
7810 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7812 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7815 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7816 g_free(compose->privacy_system);
7817 compose->privacy_system = NULL;
7818 if (systemid != NULL) {
7819 compose->privacy_system = g_strdup(systemid);
7821 can_sign = privacy_system_can_sign(systemid);
7822 can_encrypt = privacy_system_can_encrypt(systemid);
7825 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7827 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7828 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7831 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7833 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7834 GtkWidget *menuitem = NULL;
7836 gboolean can_sign = FALSE, can_encrypt = FALSE;
7837 gboolean found = FALSE;
7839 if (compose->privacy_system != NULL) {
7841 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7842 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7843 cm_return_if_fail(menuitem != NULL);
7845 amenu = GTK_MENU_SHELL(menuitem)->children;
7847 while (amenu != NULL) {
7848 GList *alist = amenu->next;
7850 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7851 if (systemid != NULL) {
7852 if (strcmp(systemid, compose->privacy_system) == 0 &&
7853 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7854 menuitem = GTK_WIDGET(amenu->data);
7856 can_sign = privacy_system_can_sign(systemid);
7857 can_encrypt = privacy_system_can_encrypt(systemid);
7861 } else if (strlen(compose->privacy_system) == 0 &&
7862 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7863 menuitem = GTK_WIDGET(amenu->data);
7866 can_encrypt = FALSE;
7873 if (menuitem != NULL)
7874 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7876 if (warn && !found && strlen(compose->privacy_system)) {
7877 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7878 "will not be able to sign or encrypt this message."),
7879 compose->privacy_system);
7883 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7884 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7887 static void compose_set_out_encoding(Compose *compose)
7889 CharSet out_encoding;
7890 const gchar *branch = NULL;
7891 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7893 switch(out_encoding) {
7894 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7895 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7896 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7897 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7898 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7899 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7900 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7901 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7902 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7903 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7904 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7905 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7906 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7907 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7908 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7909 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7910 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7911 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7912 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7913 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7914 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7915 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7916 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7917 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7918 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7919 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7920 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7921 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7922 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7923 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7924 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7925 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7927 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7930 static void compose_set_template_menu(Compose *compose)
7932 GSList *tmpl_list, *cur;
7936 tmpl_list = template_get_config();
7938 menu = gtk_menu_new();
7940 gtk_menu_set_accel_group (GTK_MENU (menu),
7941 gtk_ui_manager_get_accel_group(compose->ui_manager));
7942 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7943 Template *tmpl = (Template *)cur->data;
7944 gchar *accel_path = NULL;
7945 item = gtk_menu_item_new_with_label(tmpl->name);
7946 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7947 g_signal_connect(G_OBJECT(item), "activate",
7948 G_CALLBACK(compose_template_activate_cb),
7950 g_object_set_data(G_OBJECT(item), "template", tmpl);
7951 gtk_widget_show(item);
7952 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7953 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7957 gtk_widget_show(menu);
7958 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7961 void compose_update_actions_menu(Compose *compose)
7963 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7966 static void compose_update_privacy_systems_menu(Compose *compose)
7968 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7969 GSList *systems, *cur;
7971 GtkWidget *system_none;
7973 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7974 GtkWidget *privacy_menu = gtk_menu_new();
7976 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7977 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7979 g_signal_connect(G_OBJECT(system_none), "activate",
7980 G_CALLBACK(compose_set_privacy_system_cb), compose);
7982 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7983 gtk_widget_show(system_none);
7985 systems = privacy_get_system_ids();
7986 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7987 gchar *systemid = cur->data;
7989 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7990 widget = gtk_radio_menu_item_new_with_label(group,
7991 privacy_system_get_name(systemid));
7992 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7993 g_strdup(systemid), g_free);
7994 g_signal_connect(G_OBJECT(widget), "activate",
7995 G_CALLBACK(compose_set_privacy_system_cb), compose);
7997 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
7998 gtk_widget_show(widget);
8001 g_slist_free(systems);
8002 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8003 gtk_widget_show_all(privacy_menu);
8004 gtk_widget_show_all(privacy_menuitem);
8007 void compose_reflect_prefs_all(void)
8012 for (cur = compose_list; cur != NULL; cur = cur->next) {
8013 compose = (Compose *)cur->data;
8014 compose_set_template_menu(compose);
8018 void compose_reflect_prefs_pixmap_theme(void)
8023 for (cur = compose_list; cur != NULL; cur = cur->next) {
8024 compose = (Compose *)cur->data;
8025 toolbar_update(TOOLBAR_COMPOSE, compose);
8029 static const gchar *compose_quote_char_from_context(Compose *compose)
8031 const gchar *qmark = NULL;
8033 cm_return_val_if_fail(compose != NULL, NULL);
8035 switch (compose->mode) {
8036 /* use forward-specific quote char */
8037 case COMPOSE_FORWARD:
8038 case COMPOSE_FORWARD_AS_ATTACH:
8039 case COMPOSE_FORWARD_INLINE:
8040 if (compose->folder && compose->folder->prefs &&
8041 compose->folder->prefs->forward_with_format)
8042 qmark = compose->folder->prefs->forward_quotemark;
8043 else if (compose->account->forward_with_format)
8044 qmark = compose->account->forward_quotemark;
8046 qmark = prefs_common.fw_quotemark;
8049 /* use reply-specific quote char in all other modes */
8051 if (compose->folder && compose->folder->prefs &&
8052 compose->folder->prefs->reply_with_format)
8053 qmark = compose->folder->prefs->reply_quotemark;
8054 else if (compose->account->reply_with_format)
8055 qmark = compose->account->reply_quotemark;
8057 qmark = prefs_common.quotemark;
8061 if (qmark == NULL || *qmark == '\0')
8067 static void compose_template_apply(Compose *compose, Template *tmpl,
8071 GtkTextBuffer *buffer;
8075 gchar *parsed_str = NULL;
8076 gint cursor_pos = 0;
8077 const gchar *err_msg = _("The body of the template has an error at line %d.");
8080 /* process the body */
8082 text = GTK_TEXT_VIEW(compose->text);
8083 buffer = gtk_text_view_get_buffer(text);
8086 qmark = compose_quote_char_from_context(compose);
8088 if (compose->replyinfo != NULL) {
8091 gtk_text_buffer_set_text(buffer, "", -1);
8092 mark = gtk_text_buffer_get_insert(buffer);
8093 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8095 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8096 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8098 } else if (compose->fwdinfo != NULL) {
8101 gtk_text_buffer_set_text(buffer, "", -1);
8102 mark = gtk_text_buffer_get_insert(buffer);
8103 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8105 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8106 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8109 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8111 GtkTextIter start, end;
8114 gtk_text_buffer_get_start_iter(buffer, &start);
8115 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8116 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8118 /* clear the buffer now */
8120 gtk_text_buffer_set_text(buffer, "", -1);
8122 parsed_str = compose_quote_fmt(compose, dummyinfo,
8123 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8124 procmsg_msginfo_free( dummyinfo );
8130 gtk_text_buffer_set_text(buffer, "", -1);
8131 mark = gtk_text_buffer_get_insert(buffer);
8132 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8135 if (replace && parsed_str && compose->account->auto_sig)
8136 compose_insert_sig(compose, FALSE);
8138 if (replace && parsed_str) {
8139 gtk_text_buffer_get_start_iter(buffer, &iter);
8140 gtk_text_buffer_place_cursor(buffer, &iter);
8144 cursor_pos = quote_fmt_get_cursor_pos();
8145 compose->set_cursor_pos = cursor_pos;
8146 if (cursor_pos == -1)
8148 gtk_text_buffer_get_start_iter(buffer, &iter);
8149 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8150 gtk_text_buffer_place_cursor(buffer, &iter);
8153 /* process the other fields */
8155 compose_template_apply_fields(compose, tmpl);
8156 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8157 quote_fmt_reset_vartable();
8158 compose_changed_cb(NULL, compose);
8161 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8162 gtkaspell_highlight_all(compose->gtkaspell);
8166 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8168 MsgInfo* dummyinfo = NULL;
8169 MsgInfo *msginfo = NULL;
8172 if (compose->replyinfo != NULL)
8173 msginfo = compose->replyinfo;
8174 else if (compose->fwdinfo != NULL)
8175 msginfo = compose->fwdinfo;
8177 dummyinfo = compose_msginfo_new_from_compose(compose);
8178 msginfo = dummyinfo;
8181 if (tmpl->from && *tmpl->from != '\0') {
8183 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8184 compose->gtkaspell);
8186 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8188 quote_fmt_scan_string(tmpl->from);
8191 buf = quote_fmt_get_buffer();
8193 alertpanel_error(_("Template From format error."));
8195 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8199 if (tmpl->to && *tmpl->to != '\0') {
8201 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8202 compose->gtkaspell);
8204 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8206 quote_fmt_scan_string(tmpl->to);
8209 buf = quote_fmt_get_buffer();
8211 alertpanel_error(_("Template To format error."));
8213 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8217 if (tmpl->cc && *tmpl->cc != '\0') {
8219 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8220 compose->gtkaspell);
8222 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8224 quote_fmt_scan_string(tmpl->cc);
8227 buf = quote_fmt_get_buffer();
8229 alertpanel_error(_("Template Cc format error."));
8231 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8235 if (tmpl->bcc && *tmpl->bcc != '\0') {
8237 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8238 compose->gtkaspell);
8240 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8242 quote_fmt_scan_string(tmpl->bcc);
8245 buf = quote_fmt_get_buffer();
8247 alertpanel_error(_("Template Bcc format error."));
8249 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8253 /* process the subject */
8254 if (tmpl->subject && *tmpl->subject != '\0') {
8256 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8257 compose->gtkaspell);
8259 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8261 quote_fmt_scan_string(tmpl->subject);
8264 buf = quote_fmt_get_buffer();
8266 alertpanel_error(_("Template subject format error."));
8268 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8272 procmsg_msginfo_free( dummyinfo );
8275 static void compose_destroy(Compose *compose)
8277 GtkTextBuffer *buffer;
8278 GtkClipboard *clipboard;
8280 compose_list = g_list_remove(compose_list, compose);
8282 if (compose->updating) {
8283 debug_print("danger, not destroying anything now\n");
8284 compose->deferred_destroy = TRUE;
8287 /* NOTE: address_completion_end() does nothing with the window
8288 * however this may change. */
8289 address_completion_end(compose->window);
8291 slist_free_strings(compose->to_list);
8292 g_slist_free(compose->to_list);
8293 slist_free_strings(compose->newsgroup_list);
8294 g_slist_free(compose->newsgroup_list);
8295 slist_free_strings(compose->header_list);
8296 g_slist_free(compose->header_list);
8298 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8300 g_hash_table_destroy(compose->email_hashtable);
8302 procmsg_msginfo_free(compose->targetinfo);
8303 procmsg_msginfo_free(compose->replyinfo);
8304 procmsg_msginfo_free(compose->fwdinfo);
8306 g_free(compose->replyto);
8307 g_free(compose->cc);
8308 g_free(compose->bcc);
8309 g_free(compose->newsgroups);
8310 g_free(compose->followup_to);
8312 g_free(compose->ml_post);
8314 g_free(compose->inreplyto);
8315 g_free(compose->references);
8316 g_free(compose->msgid);
8317 g_free(compose->boundary);
8319 g_free(compose->redirect_filename);
8320 if (compose->undostruct)
8321 undo_destroy(compose->undostruct);
8323 g_free(compose->sig_str);
8325 g_free(compose->exteditor_file);
8327 g_free(compose->orig_charset);
8329 g_free(compose->privacy_system);
8331 if (addressbook_get_target_compose() == compose)
8332 addressbook_set_target_compose(NULL);
8335 if (compose->gtkaspell) {
8336 gtkaspell_delete(compose->gtkaspell);
8337 compose->gtkaspell = NULL;
8341 if (!compose->batch) {
8342 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8343 prefs_common.compose_height = compose->window->allocation.height;
8346 if (!gtk_widget_get_parent(compose->paned))
8347 gtk_widget_destroy(compose->paned);
8348 gtk_widget_destroy(compose->popupmenu);
8350 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8351 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8352 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8354 gtk_widget_destroy(compose->window);
8355 toolbar_destroy(compose->toolbar);
8356 g_free(compose->toolbar);
8357 g_mutex_free(compose->mutex);
8361 static void compose_attach_info_free(AttachInfo *ainfo)
8363 g_free(ainfo->file);
8364 g_free(ainfo->content_type);
8365 g_free(ainfo->name);
8369 static void compose_attach_update_label(Compose *compose)
8374 GtkTreeModel *model;
8379 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8380 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8381 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8385 while(gtk_tree_model_iter_next(model, &iter))
8388 text = g_strdup_printf("(%d)", i);
8389 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8393 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8395 Compose *compose = (Compose *)data;
8396 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8397 GtkTreeSelection *selection;
8399 GtkTreeModel *model;
8401 selection = gtk_tree_view_get_selection(tree_view);
8402 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8407 for (cur = sel; cur != NULL; cur = cur->next) {
8408 GtkTreePath *path = cur->data;
8409 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8412 gtk_tree_path_free(path);
8415 for (cur = sel; cur != NULL; cur = cur->next) {
8416 GtkTreeRowReference *ref = cur->data;
8417 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8420 if (gtk_tree_model_get_iter(model, &iter, path))
8421 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8423 gtk_tree_path_free(path);
8424 gtk_tree_row_reference_free(ref);
8428 compose_attach_update_label(compose);
8431 static struct _AttachProperty
8434 GtkWidget *mimetype_entry;
8435 GtkWidget *encoding_optmenu;
8436 GtkWidget *path_entry;
8437 GtkWidget *filename_entry;
8439 GtkWidget *cancel_btn;
8442 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8444 gtk_tree_path_free((GtkTreePath *)ptr);
8447 static void compose_attach_property(GtkAction *action, gpointer data)
8449 Compose *compose = (Compose *)data;
8450 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8452 GtkComboBox *optmenu;
8453 GtkTreeSelection *selection;
8455 GtkTreeModel *model;
8458 static gboolean cancelled;
8460 /* only if one selected */
8461 selection = gtk_tree_view_get_selection(tree_view);
8462 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8465 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8469 path = (GtkTreePath *) sel->data;
8470 gtk_tree_model_get_iter(model, &iter, path);
8471 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8474 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8480 if (!attach_prop.window)
8481 compose_attach_property_create(&cancelled);
8482 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8483 gtk_widget_grab_focus(attach_prop.ok_btn);
8484 gtk_widget_show(attach_prop.window);
8485 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8487 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8488 if (ainfo->encoding == ENC_UNKNOWN)
8489 combobox_select_by_data(optmenu, ENC_BASE64);
8491 combobox_select_by_data(optmenu, ainfo->encoding);
8493 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8494 ainfo->content_type ? ainfo->content_type : "");
8495 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8496 ainfo->file ? ainfo->file : "");
8497 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8498 ainfo->name ? ainfo->name : "");
8501 const gchar *entry_text;
8503 gchar *cnttype = NULL;
8510 gtk_widget_hide(attach_prop.window);
8511 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8516 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8517 if (*entry_text != '\0') {
8520 text = g_strstrip(g_strdup(entry_text));
8521 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8522 cnttype = g_strdup(text);
8525 alertpanel_error(_("Invalid MIME type."));
8531 ainfo->encoding = combobox_get_active_data(optmenu);
8533 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8534 if (*entry_text != '\0') {
8535 if (is_file_exist(entry_text) &&
8536 (size = get_file_size(entry_text)) > 0)
8537 file = g_strdup(entry_text);
8540 (_("File doesn't exist or is empty."));
8546 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8547 if (*entry_text != '\0') {
8548 g_free(ainfo->name);
8549 ainfo->name = g_strdup(entry_text);
8553 g_free(ainfo->content_type);
8554 ainfo->content_type = cnttype;
8557 g_free(ainfo->file);
8561 ainfo->size = (goffset)size;
8563 /* update tree store */
8564 text = to_human_readable(ainfo->size);
8565 gtk_tree_model_get_iter(model, &iter, path);
8566 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8567 COL_MIMETYPE, ainfo->content_type,
8569 COL_NAME, ainfo->name,
8575 gtk_tree_path_free(path);
8578 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8580 label = gtk_label_new(str); \
8581 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8582 GTK_FILL, 0, 0, 0); \
8583 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8585 entry = gtk_entry_new(); \
8586 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8587 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8590 static void compose_attach_property_create(gboolean *cancelled)
8596 GtkWidget *mimetype_entry;
8599 GtkListStore *optmenu_menu;
8600 GtkWidget *path_entry;
8601 GtkWidget *filename_entry;
8604 GtkWidget *cancel_btn;
8605 GList *mime_type_list, *strlist;
8608 debug_print("Creating attach_property window...\n");
8610 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8611 gtk_widget_set_size_request(window, 480, -1);
8612 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8613 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8614 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8615 g_signal_connect(G_OBJECT(window), "delete_event",
8616 G_CALLBACK(attach_property_delete_event),
8618 g_signal_connect(G_OBJECT(window), "key_press_event",
8619 G_CALLBACK(attach_property_key_pressed),
8622 vbox = gtk_vbox_new(FALSE, 8);
8623 gtk_container_add(GTK_CONTAINER(window), vbox);
8625 table = gtk_table_new(4, 2, FALSE);
8626 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8627 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8628 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8630 label = gtk_label_new(_("MIME type"));
8631 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8633 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8634 mimetype_entry = gtk_combo_box_entry_new_text();
8635 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8636 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8638 /* stuff with list */
8639 mime_type_list = procmime_get_mime_type_list();
8641 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8642 MimeType *type = (MimeType *) mime_type_list->data;
8645 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8647 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8650 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8651 (GCompareFunc)strcmp2);
8654 for (mime_type_list = strlist; mime_type_list != NULL;
8655 mime_type_list = mime_type_list->next) {
8656 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8657 g_free(mime_type_list->data);
8659 g_list_free(strlist);
8660 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8661 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8663 label = gtk_label_new(_("Encoding"));
8664 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8666 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8668 hbox = gtk_hbox_new(FALSE, 0);
8669 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8670 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8672 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8673 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8675 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8676 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8677 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8678 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8679 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8681 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8683 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8684 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8686 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8687 &ok_btn, GTK_STOCK_OK,
8689 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8690 gtk_widget_grab_default(ok_btn);
8692 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8693 G_CALLBACK(attach_property_ok),
8695 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8696 G_CALLBACK(attach_property_cancel),
8699 gtk_widget_show_all(vbox);
8701 attach_prop.window = window;
8702 attach_prop.mimetype_entry = mimetype_entry;
8703 attach_prop.encoding_optmenu = optmenu;
8704 attach_prop.path_entry = path_entry;
8705 attach_prop.filename_entry = filename_entry;
8706 attach_prop.ok_btn = ok_btn;
8707 attach_prop.cancel_btn = cancel_btn;
8710 #undef SET_LABEL_AND_ENTRY
8712 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8718 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8724 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8725 gboolean *cancelled)
8733 static gboolean attach_property_key_pressed(GtkWidget *widget,
8735 gboolean *cancelled)
8737 if (event && event->keyval == GDK_Escape) {
8741 if (event && event->keyval == GDK_Return) {
8749 static void compose_exec_ext_editor(Compose *compose)
8756 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8757 G_DIR_SEPARATOR, compose);
8759 if (pipe(pipe_fds) < 0) {
8765 if ((pid = fork()) < 0) {
8772 /* close the write side of the pipe */
8775 compose->exteditor_file = g_strdup(tmp);
8776 compose->exteditor_pid = pid;
8778 compose_set_ext_editor_sensitive(compose, FALSE);
8781 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8783 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8785 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8789 } else { /* process-monitoring process */
8795 /* close the read side of the pipe */
8798 if (compose_write_body_to_file(compose, tmp) < 0) {
8799 fd_write_all(pipe_fds[1], "2\n", 2);
8803 pid_ed = compose_exec_ext_editor_real(tmp);
8805 fd_write_all(pipe_fds[1], "1\n", 2);
8809 /* wait until editor is terminated */
8810 waitpid(pid_ed, NULL, 0);
8812 fd_write_all(pipe_fds[1], "0\n", 2);
8819 #endif /* G_OS_UNIX */
8823 static gint compose_exec_ext_editor_real(const gchar *file)
8830 cm_return_val_if_fail(file != NULL, -1);
8832 if ((pid = fork()) < 0) {
8837 if (pid != 0) return pid;
8839 /* grandchild process */
8841 if (setpgid(0, getppid()))
8844 if (prefs_common_get_ext_editor_cmd() &&
8845 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8846 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8847 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8849 if (prefs_common_get_ext_editor_cmd())
8850 g_warning("External editor command-line is invalid: '%s'\n",
8851 prefs_common_get_ext_editor_cmd());
8852 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8855 cmdline = strsplit_with_quote(buf, " ", 1024);
8856 execvp(cmdline[0], cmdline);
8859 g_strfreev(cmdline);
8864 static gboolean compose_ext_editor_kill(Compose *compose)
8866 pid_t pgid = compose->exteditor_pid * -1;
8869 ret = kill(pgid, 0);
8871 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8875 msg = g_strdup_printf
8876 (_("The external editor is still working.\n"
8877 "Force terminating the process?\n"
8878 "process group id: %d"), -pgid);
8879 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8880 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8884 if (val == G_ALERTALTERNATE) {
8885 g_source_remove(compose->exteditor_tag);
8886 g_io_channel_shutdown(compose->exteditor_ch,
8888 g_io_channel_unref(compose->exteditor_ch);
8890 if (kill(pgid, SIGTERM) < 0) perror("kill");
8891 waitpid(compose->exteditor_pid, NULL, 0);
8893 g_warning("Terminated process group id: %d", -pgid);
8894 g_warning("Temporary file: %s",
8895 compose->exteditor_file);
8897 compose_set_ext_editor_sensitive(compose, TRUE);
8899 g_free(compose->exteditor_file);
8900 compose->exteditor_file = NULL;
8901 compose->exteditor_pid = -1;
8902 compose->exteditor_ch = NULL;
8903 compose->exteditor_tag = -1;
8911 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8915 Compose *compose = (Compose *)data;
8918 debug_print(_("Compose: input from monitoring process\n"));
8920 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8922 g_io_channel_shutdown(source, FALSE, NULL);
8923 g_io_channel_unref(source);
8925 waitpid(compose->exteditor_pid, NULL, 0);
8927 if (buf[0] == '0') { /* success */
8928 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8929 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8931 gtk_text_buffer_set_text(buffer, "", -1);
8932 compose_insert_file(compose, compose->exteditor_file);
8933 compose_changed_cb(NULL, compose);
8934 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8936 if (claws_unlink(compose->exteditor_file) < 0)
8937 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8938 } else if (buf[0] == '1') { /* failed */
8939 g_warning("Couldn't exec external editor\n");
8940 if (claws_unlink(compose->exteditor_file) < 0)
8941 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8942 } else if (buf[0] == '2') {
8943 g_warning("Couldn't write to file\n");
8944 } else if (buf[0] == '3') {
8945 g_warning("Pipe read failed\n");
8948 compose_set_ext_editor_sensitive(compose, TRUE);
8950 g_free(compose->exteditor_file);
8951 compose->exteditor_file = NULL;
8952 compose->exteditor_pid = -1;
8953 compose->exteditor_ch = NULL;
8954 compose->exteditor_tag = -1;
8959 static void compose_set_ext_editor_sensitive(Compose *compose,
8962 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8963 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8964 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8965 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8966 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8967 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8968 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8970 gtk_widget_set_sensitive(compose->text, sensitive);
8971 if (compose->toolbar->send_btn)
8972 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8973 if (compose->toolbar->sendl_btn)
8974 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8975 if (compose->toolbar->draft_btn)
8976 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8977 if (compose->toolbar->insert_btn)
8978 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8979 if (compose->toolbar->sig_btn)
8980 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8981 if (compose->toolbar->exteditor_btn)
8982 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8983 if (compose->toolbar->linewrap_current_btn)
8984 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8985 if (compose->toolbar->linewrap_all_btn)
8986 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8988 #endif /* G_OS_UNIX */
8991 * compose_undo_state_changed:
8993 * Change the sensivity of the menuentries undo and redo
8995 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8996 gint redo_state, gpointer data)
8998 Compose *compose = (Compose *)data;
9000 switch (undo_state) {
9001 case UNDO_STATE_TRUE:
9002 if (!undostruct->undo_state) {
9003 undostruct->undo_state = TRUE;
9004 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9007 case UNDO_STATE_FALSE:
9008 if (undostruct->undo_state) {
9009 undostruct->undo_state = FALSE;
9010 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9013 case UNDO_STATE_UNCHANGED:
9015 case UNDO_STATE_REFRESH:
9016 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9019 g_warning("Undo state not recognized");
9023 switch (redo_state) {
9024 case UNDO_STATE_TRUE:
9025 if (!undostruct->redo_state) {
9026 undostruct->redo_state = TRUE;
9027 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9030 case UNDO_STATE_FALSE:
9031 if (undostruct->redo_state) {
9032 undostruct->redo_state = FALSE;
9033 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9036 case UNDO_STATE_UNCHANGED:
9038 case UNDO_STATE_REFRESH:
9039 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9042 g_warning("Redo state not recognized");
9047 /* callback functions */
9049 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9050 * includes "non-client" (windows-izm) in calculation, so this calculation
9051 * may not be accurate.
9053 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9054 GtkAllocation *allocation,
9055 GtkSHRuler *shruler)
9057 if (prefs_common.show_ruler) {
9058 gint char_width = 0, char_height = 0;
9059 gint line_width_in_chars;
9061 gtkut_get_font_size(GTK_WIDGET(widget),
9062 &char_width, &char_height);
9063 line_width_in_chars =
9064 (allocation->width - allocation->x) / char_width;
9066 /* got the maximum */
9067 gtk_ruler_set_range(GTK_RULER(shruler),
9068 0.0, line_width_in_chars, 0,
9069 /*line_width_in_chars*/ char_width);
9076 ComposeEntryType header;
9078 ComposePrefType type;
9079 gboolean entry_marked;
9082 static void account_activated(GtkComboBox *optmenu, gpointer data)
9084 Compose *compose = (Compose *)data;
9087 gchar *folderidentifier;
9088 gint account_id = 0;
9091 GSList *list, *saved_list = NULL;
9092 HeaderEntryState *state;
9093 GtkRcStyle *style = NULL;
9094 static GdkColor yellow;
9095 static gboolean color_set = FALSE;
9097 /* Get ID of active account in the combo box */
9098 menu = gtk_combo_box_get_model(optmenu);
9099 gtk_combo_box_get_active_iter(optmenu, &iter);
9100 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9102 ac = account_find_from_id(account_id);
9103 cm_return_if_fail(ac != NULL);
9105 if (ac != compose->account) {
9106 compose_select_account(compose, ac, FALSE);
9108 for (list = compose->header_list; list; list = list->next) {
9109 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9111 if (hentry->type == PREF_ACCOUNT || !list->next) {
9112 compose_destroy_headerentry(compose, hentry);
9116 state = g_malloc0(sizeof(HeaderEntryState));
9117 state->header = combobox_get_active_data(
9118 GTK_COMBO_BOX(hentry->combo));
9119 state->entry = gtk_editable_get_chars(
9120 GTK_EDITABLE(hentry->entry), 0, -1);
9121 state->type = hentry->type;
9124 gdk_color_parse("#f5f6be", &yellow);
9125 color_set = gdk_colormap_alloc_color(
9126 gdk_colormap_get_system(),
9127 &yellow, FALSE, TRUE);
9130 style = gtk_widget_get_modifier_style(hentry->entry);
9131 state->entry_marked = gdk_color_equal(&yellow,
9132 &style->base[GTK_STATE_NORMAL]);
9134 saved_list = g_slist_append(saved_list, state);
9135 compose_destroy_headerentry(compose, hentry);
9138 compose->header_last = NULL;
9139 g_slist_free(compose->header_list);
9140 compose->header_list = NULL;
9141 compose->header_nextrow = 1;
9142 compose_create_header_entry(compose);
9144 if (ac->set_autocc && ac->auto_cc)
9145 compose_entry_append(compose, ac->auto_cc,
9146 COMPOSE_CC, PREF_ACCOUNT);
9148 if (ac->set_autobcc && ac->auto_bcc)
9149 compose_entry_append(compose, ac->auto_bcc,
9150 COMPOSE_BCC, PREF_ACCOUNT);
9152 if (ac->set_autoreplyto && ac->auto_replyto)
9153 compose_entry_append(compose, ac->auto_replyto,
9154 COMPOSE_REPLYTO, PREF_ACCOUNT);
9156 for (list = saved_list; list; list = list->next) {
9157 state = (HeaderEntryState *) list->data;
9159 compose_entry_append(compose, state->entry,
9160 state->header, state->type);
9161 if (state->entry_marked)
9162 compose_entry_mark_default_to(compose, state->entry);
9164 g_free(state->entry);
9166 g_slist_free(saved_list);
9168 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9169 (ac->protocol == A_NNTP) ?
9170 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9173 /* Set message save folder */
9174 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9175 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9177 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9178 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9180 compose_set_save_to(compose, NULL);
9181 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9182 folderidentifier = folder_item_get_identifier(account_get_special_folder
9183 (compose->account, F_OUTBOX));
9184 compose_set_save_to(compose, folderidentifier);
9185 g_free(folderidentifier);
9189 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9190 GtkTreeViewColumn *column, Compose *compose)
9192 compose_attach_property(NULL, compose);
9195 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9198 Compose *compose = (Compose *)data;
9199 GtkTreeSelection *attach_selection;
9200 gint attach_nr_selected;
9202 if (!event) return FALSE;
9204 if (event->button == 3) {
9205 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9206 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9208 if (attach_nr_selected > 0)
9210 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9211 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9213 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9214 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9217 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9218 NULL, NULL, event->button, event->time);
9225 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9228 Compose *compose = (Compose *)data;
9230 if (!event) return FALSE;
9232 switch (event->keyval) {
9234 compose_attach_remove_selected(NULL, compose);
9240 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9242 toolbar_comp_set_sensitive(compose, allow);
9243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9248 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9249 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9250 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9252 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9256 static void compose_send_cb(GtkAction *action, gpointer data)
9258 Compose *compose = (Compose *)data;
9260 if (prefs_common.work_offline &&
9261 !inc_offline_should_override(TRUE,
9262 _("Claws Mail needs network access in order "
9263 "to send this email.")))
9266 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9267 g_source_remove(compose->draft_timeout_tag);
9268 compose->draft_timeout_tag = -1;
9271 compose_send(compose);
9274 static void compose_send_later_cb(GtkAction *action, gpointer data)
9276 Compose *compose = (Compose *)data;
9280 compose_allow_user_actions(compose, FALSE);
9281 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9282 compose_allow_user_actions(compose, TRUE);
9286 compose_close(compose);
9287 } else if (val == -1) {
9288 alertpanel_error(_("Could not queue message."));
9289 } else if (val == -2) {
9290 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9291 } else if (val == -3) {
9292 if (privacy_peek_error())
9293 alertpanel_error(_("Could not queue message for sending:\n\n"
9294 "Signature failed: %s"), privacy_get_error());
9295 } else if (val == -4) {
9296 alertpanel_error(_("Could not queue message for sending:\n\n"
9297 "Charset conversion failed."));
9298 } else if (val == -5) {
9299 alertpanel_error(_("Could not queue message for sending:\n\n"
9300 "Couldn't get recipient encryption key."));
9301 } else if (val == -6) {
9304 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9307 #define DRAFTED_AT_EXIT "drafted_at_exit"
9308 static void compose_register_draft(MsgInfo *info)
9310 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9311 DRAFTED_AT_EXIT, NULL);
9312 FILE *fp = g_fopen(filepath, "ab");
9315 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9323 gboolean compose_draft (gpointer data, guint action)
9325 Compose *compose = (Compose *)data;
9329 MsgFlags flag = {0, 0};
9330 static gboolean lock = FALSE;
9331 MsgInfo *newmsginfo;
9333 gboolean target_locked = FALSE;
9334 gboolean err = FALSE;
9336 if (lock) return FALSE;
9338 if (compose->sending)
9341 draft = account_get_special_folder(compose->account, F_DRAFT);
9342 cm_return_val_if_fail(draft != NULL, FALSE);
9344 if (!g_mutex_trylock(compose->mutex)) {
9345 /* we don't want to lock the mutex once it's available,
9346 * because as the only other part of compose.c locking
9347 * it is compose_close - which means once unlocked,
9348 * the compose struct will be freed */
9349 debug_print("couldn't lock mutex, probably sending\n");
9355 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9356 G_DIR_SEPARATOR, compose);
9357 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9358 FILE_OP_ERROR(tmp, "fopen");
9362 /* chmod for security */
9363 if (change_file_mode_rw(fp, tmp) < 0) {
9364 FILE_OP_ERROR(tmp, "chmod");
9365 g_warning("can't change file mode\n");
9368 /* Save draft infos */
9369 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9370 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9372 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9373 gchar *savefolderid;
9375 savefolderid = compose_get_save_to(compose);
9376 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9377 g_free(savefolderid);
9379 if (compose->return_receipt) {
9380 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9382 if (compose->privacy_system) {
9383 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9384 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9385 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9388 /* Message-ID of message replying to */
9389 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9392 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9393 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9396 /* Message-ID of message forwarding to */
9397 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9400 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9401 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9405 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9406 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9408 /* end of headers */
9409 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9416 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9420 if (fclose(fp) == EOF) {
9424 if (compose->targetinfo) {
9425 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9426 flag.perm_flags = target_locked?MSG_LOCKED:0;
9428 flag.tmp_flags = MSG_DRAFT;
9430 folder_item_scan(draft);
9431 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9432 MsgInfo *tmpinfo = NULL;
9433 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9434 if (compose->msgid) {
9435 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9438 msgnum = tmpinfo->msgnum;
9439 procmsg_msginfo_free(tmpinfo);
9440 debug_print("got draft msgnum %d from scanning\n", msgnum);
9442 debug_print("didn't get draft msgnum after scanning\n");
9445 debug_print("got draft msgnum %d from adding\n", msgnum);
9451 if (action != COMPOSE_AUTO_SAVE) {
9452 if (action != COMPOSE_DRAFT_FOR_EXIT)
9453 alertpanel_error(_("Could not save draft."));
9456 gtkut_window_popup(compose->window);
9457 val = alertpanel_full(_("Could not save draft"),
9458 _("Could not save draft.\n"
9459 "Do you want to cancel exit or discard this email?"),
9460 _("_Cancel exit"), _("_Discard email"), NULL,
9461 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9462 if (val == G_ALERTALTERNATE) {
9464 g_mutex_unlock(compose->mutex); /* must be done before closing */
9465 compose_close(compose);
9469 g_mutex_unlock(compose->mutex); /* must be done before closing */
9478 if (compose->mode == COMPOSE_REEDIT) {
9479 compose_remove_reedit_target(compose, TRUE);
9482 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9485 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9487 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9489 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9490 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9491 procmsg_msginfo_set_flags(newmsginfo, 0,
9492 MSG_HAS_ATTACHMENT);
9494 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9495 compose_register_draft(newmsginfo);
9497 procmsg_msginfo_free(newmsginfo);
9500 folder_item_scan(draft);
9502 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9504 g_mutex_unlock(compose->mutex); /* must be done before closing */
9505 compose_close(compose);
9511 path = folder_item_fetch_msg(draft, msgnum);
9513 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9516 if (g_stat(path, &s) < 0) {
9517 FILE_OP_ERROR(path, "stat");
9523 procmsg_msginfo_free(compose->targetinfo);
9524 compose->targetinfo = procmsg_msginfo_new();
9525 compose->targetinfo->msgnum = msgnum;
9526 compose->targetinfo->size = (goffset)s.st_size;
9527 compose->targetinfo->mtime = s.st_mtime;
9528 compose->targetinfo->folder = draft;
9530 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9531 compose->mode = COMPOSE_REEDIT;
9533 if (action == COMPOSE_AUTO_SAVE) {
9534 compose->autosaved_draft = compose->targetinfo;
9536 compose->modified = FALSE;
9537 compose_set_title(compose);
9541 g_mutex_unlock(compose->mutex);
9545 void compose_clear_exit_drafts(void)
9547 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9548 DRAFTED_AT_EXIT, NULL);
9549 if (is_file_exist(filepath))
9550 claws_unlink(filepath);
9555 void compose_reopen_exit_drafts(void)
9557 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9558 DRAFTED_AT_EXIT, NULL);
9559 FILE *fp = g_fopen(filepath, "rb");
9563 while (fgets(buf, sizeof(buf), fp)) {
9564 gchar **parts = g_strsplit(buf, "\t", 2);
9565 const gchar *folder = parts[0];
9566 int msgnum = parts[1] ? atoi(parts[1]):-1;
9568 if (folder && *folder && msgnum > -1) {
9569 FolderItem *item = folder_find_item_from_identifier(folder);
9570 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9572 compose_reedit(info, FALSE);
9579 compose_clear_exit_drafts();
9582 static void compose_save_cb(GtkAction *action, gpointer data)
9584 Compose *compose = (Compose *)data;
9585 compose_draft(compose, COMPOSE_KEEP_EDITING);
9586 compose->rmode = COMPOSE_REEDIT;
9589 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9591 if (compose && file_list) {
9594 for ( tmp = file_list; tmp; tmp = tmp->next) {
9595 gchar *file = (gchar *) tmp->data;
9596 gchar *utf8_filename = conv_filename_to_utf8(file);
9597 compose_attach_append(compose, file, utf8_filename, NULL);
9598 compose_changed_cb(NULL, compose);
9603 g_free(utf8_filename);
9608 static void compose_attach_cb(GtkAction *action, gpointer data)
9610 Compose *compose = (Compose *)data;
9613 if (compose->redirect_filename != NULL)
9616 file_list = filesel_select_multiple_files_open(_("Select file"));
9619 compose_attach_from_list(compose, file_list, TRUE);
9620 g_list_free(file_list);
9624 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9626 Compose *compose = (Compose *)data;
9628 gint files_inserted = 0;
9630 file_list = filesel_select_multiple_files_open(_("Select file"));
9635 for ( tmp = file_list; tmp; tmp = tmp->next) {
9636 gchar *file = (gchar *) tmp->data;
9637 gchar *filedup = g_strdup(file);
9638 gchar *shortfile = g_path_get_basename(filedup);
9639 ComposeInsertResult res;
9640 /* insert the file if the file is short or if the user confirmed that
9641 he/she wants to insert the large file */
9642 res = compose_insert_file(compose, file);
9643 if (res == COMPOSE_INSERT_READ_ERROR) {
9644 alertpanel_error(_("File '%s' could not be read."), shortfile);
9645 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9646 alertpanel_error(_("File '%s' contained invalid characters\n"
9647 "for the current encoding, insertion may be incorrect."),
9649 } else if (res == COMPOSE_INSERT_SUCCESS)
9656 g_list_free(file_list);
9660 if (files_inserted > 0 && compose->gtkaspell &&
9661 compose->gtkaspell->check_while_typing)
9662 gtkaspell_highlight_all(compose->gtkaspell);
9666 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9668 Compose *compose = (Compose *)data;
9670 compose_insert_sig(compose, FALSE);
9673 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9677 Compose *compose = (Compose *)data;
9679 gtkut_widget_get_uposition(widget, &x, &y);
9680 if (!compose->batch) {
9681 prefs_common.compose_x = x;
9682 prefs_common.compose_y = y;
9684 if (compose->sending || compose->updating)
9686 compose_close_cb(NULL, compose);
9690 void compose_close_toolbar(Compose *compose)
9692 compose_close_cb(NULL, compose);
9695 static void compose_close_cb(GtkAction *action, gpointer data)
9697 Compose *compose = (Compose *)data;
9701 if (compose->exteditor_tag != -1) {
9702 if (!compose_ext_editor_kill(compose))
9707 if (compose->modified) {
9708 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9709 if (!g_mutex_trylock(compose->mutex)) {
9710 /* we don't want to lock the mutex once it's available,
9711 * because as the only other part of compose.c locking
9712 * it is compose_close - which means once unlocked,
9713 * the compose struct will be freed */
9714 debug_print("couldn't lock mutex, probably sending\n");
9718 val = alertpanel(_("Discard message"),
9719 _("This message has been modified. Discard it?"),
9720 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9722 val = alertpanel(_("Save changes"),
9723 _("This message has been modified. Save the latest changes?"),
9724 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9726 g_mutex_unlock(compose->mutex);
9728 case G_ALERTDEFAULT:
9729 if (prefs_common.autosave && !reedit)
9730 compose_remove_draft(compose);
9732 case G_ALERTALTERNATE:
9733 compose_draft(data, COMPOSE_QUIT_EDITING);
9740 compose_close(compose);
9743 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9745 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9746 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9747 Compose *compose = (Compose *) data;
9750 compose->out_encoding = (CharSet)value;
9753 static void compose_address_cb(GtkAction *action, gpointer data)
9755 Compose *compose = (Compose *)data;
9757 addressbook_open(compose);
9760 static void about_show_cb(GtkAction *action, gpointer data)
9765 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9767 Compose *compose = (Compose *)data;
9772 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9773 cm_return_if_fail(tmpl != NULL);
9775 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9777 val = alertpanel(_("Apply template"), msg,
9778 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9781 if (val == G_ALERTDEFAULT)
9782 compose_template_apply(compose, tmpl, TRUE);
9783 else if (val == G_ALERTALTERNATE)
9784 compose_template_apply(compose, tmpl, FALSE);
9787 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9789 Compose *compose = (Compose *)data;
9791 compose_exec_ext_editor(compose);
9794 static void compose_undo_cb(GtkAction *action, gpointer data)
9796 Compose *compose = (Compose *)data;
9797 gboolean prev_autowrap = compose->autowrap;
9799 compose->autowrap = FALSE;
9800 undo_undo(compose->undostruct);
9801 compose->autowrap = prev_autowrap;
9804 static void compose_redo_cb(GtkAction *action, gpointer data)
9806 Compose *compose = (Compose *)data;
9807 gboolean prev_autowrap = compose->autowrap;
9809 compose->autowrap = FALSE;
9810 undo_redo(compose->undostruct);
9811 compose->autowrap = prev_autowrap;
9814 static void entry_cut_clipboard(GtkWidget *entry)
9816 if (GTK_IS_EDITABLE(entry))
9817 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9818 else if (GTK_IS_TEXT_VIEW(entry))
9819 gtk_text_buffer_cut_clipboard(
9820 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9821 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9825 static void entry_copy_clipboard(GtkWidget *entry)
9827 if (GTK_IS_EDITABLE(entry))
9828 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9829 else if (GTK_IS_TEXT_VIEW(entry))
9830 gtk_text_buffer_copy_clipboard(
9831 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9832 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9835 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9836 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9838 if (GTK_IS_TEXT_VIEW(entry)) {
9839 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9840 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9841 GtkTextIter start_iter, end_iter;
9843 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9845 if (contents == NULL)
9848 /* we shouldn't delete the selection when middle-click-pasting, or we
9849 * can't mid-click-paste our own selection */
9850 if (clip != GDK_SELECTION_PRIMARY) {
9851 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9852 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9855 if (insert_place == NULL) {
9856 /* if insert_place isn't specified, insert at the cursor.
9857 * used for Ctrl-V pasting */
9858 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9859 start = gtk_text_iter_get_offset(&start_iter);
9860 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9862 /* if insert_place is specified, paste here.
9863 * used for mid-click-pasting */
9864 start = gtk_text_iter_get_offset(insert_place);
9865 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9866 if (prefs_common.primary_paste_unselects)
9867 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9871 /* paste unwrapped: mark the paste so it's not wrapped later */
9872 end = start + strlen(contents);
9873 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9874 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9875 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9876 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9877 /* rewrap paragraph now (after a mid-click-paste) */
9878 mark_start = gtk_text_buffer_get_insert(buffer);
9879 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9880 gtk_text_iter_backward_char(&start_iter);
9881 compose_beautify_paragraph(compose, &start_iter, TRUE);
9883 } else if (GTK_IS_EDITABLE(entry))
9884 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9886 compose->modified = TRUE;
9889 static void entry_allsel(GtkWidget *entry)
9891 if (GTK_IS_EDITABLE(entry))
9892 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9893 else if (GTK_IS_TEXT_VIEW(entry)) {
9894 GtkTextIter startiter, enditer;
9895 GtkTextBuffer *textbuf;
9897 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9898 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9899 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9901 gtk_text_buffer_move_mark_by_name(textbuf,
9902 "selection_bound", &startiter);
9903 gtk_text_buffer_move_mark_by_name(textbuf,
9904 "insert", &enditer);
9908 static void compose_cut_cb(GtkAction *action, gpointer data)
9910 Compose *compose = (Compose *)data;
9911 if (compose->focused_editable
9912 #ifndef GENERIC_UMPC
9913 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9916 entry_cut_clipboard(compose->focused_editable);
9919 static void compose_copy_cb(GtkAction *action, gpointer data)
9921 Compose *compose = (Compose *)data;
9922 if (compose->focused_editable
9923 #ifndef GENERIC_UMPC
9924 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9927 entry_copy_clipboard(compose->focused_editable);
9930 static void compose_paste_cb(GtkAction *action, gpointer data)
9932 Compose *compose = (Compose *)data;
9934 GtkTextBuffer *buffer;
9936 if (compose->focused_editable &&
9937 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9938 entry_paste_clipboard(compose, compose->focused_editable,
9939 prefs_common.linewrap_pastes,
9940 GDK_SELECTION_CLIPBOARD, NULL);
9944 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9945 compose->gtkaspell &&
9946 compose->gtkaspell->check_while_typing)
9947 gtkaspell_highlight_all(compose->gtkaspell);
9951 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9953 Compose *compose = (Compose *)data;
9954 gint wrap_quote = prefs_common.linewrap_quote;
9955 if (compose->focused_editable
9956 #ifndef GENERIC_UMPC
9957 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9960 /* let text_insert() (called directly or at a later time
9961 * after the gtk_editable_paste_clipboard) know that
9962 * text is to be inserted as a quotation. implemented
9963 * by using a simple refcount... */
9964 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9965 G_OBJECT(compose->focused_editable),
9966 "paste_as_quotation"));
9967 g_object_set_data(G_OBJECT(compose->focused_editable),
9968 "paste_as_quotation",
9969 GINT_TO_POINTER(paste_as_quotation + 1));
9970 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9971 entry_paste_clipboard(compose, compose->focused_editable,
9972 prefs_common.linewrap_pastes,
9973 GDK_SELECTION_CLIPBOARD, NULL);
9974 prefs_common.linewrap_quote = wrap_quote;
9978 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9980 Compose *compose = (Compose *)data;
9982 GtkTextBuffer *buffer;
9984 if (compose->focused_editable
9985 #ifndef GENERIC_UMPC
9986 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9989 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9990 GDK_SELECTION_CLIPBOARD, NULL);
9994 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9995 compose->gtkaspell &&
9996 compose->gtkaspell->check_while_typing)
9997 gtkaspell_highlight_all(compose->gtkaspell);
10001 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10003 Compose *compose = (Compose *)data;
10004 gint prev_autowrap;
10005 GtkTextBuffer *buffer;
10007 if (compose->focused_editable
10008 #ifndef GENERIC_UMPC
10009 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10012 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10013 GDK_SELECTION_CLIPBOARD, NULL);
10017 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10018 compose->gtkaspell &&
10019 compose->gtkaspell->check_while_typing)
10020 gtkaspell_highlight_all(compose->gtkaspell);
10024 static void compose_allsel_cb(GtkAction *action, gpointer data)
10026 Compose *compose = (Compose *)data;
10027 if (compose->focused_editable
10028 #ifndef GENERIC_UMPC
10029 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10032 entry_allsel(compose->focused_editable);
10035 static void textview_move_beginning_of_line (GtkTextView *text)
10037 GtkTextBuffer *buffer;
10041 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10043 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10044 mark = gtk_text_buffer_get_insert(buffer);
10045 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10046 gtk_text_iter_set_line_offset(&ins, 0);
10047 gtk_text_buffer_place_cursor(buffer, &ins);
10050 static void textview_move_forward_character (GtkTextView *text)
10052 GtkTextBuffer *buffer;
10056 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10058 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10059 mark = gtk_text_buffer_get_insert(buffer);
10060 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10061 if (gtk_text_iter_forward_cursor_position(&ins))
10062 gtk_text_buffer_place_cursor(buffer, &ins);
10065 static void textview_move_backward_character (GtkTextView *text)
10067 GtkTextBuffer *buffer;
10071 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10073 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10074 mark = gtk_text_buffer_get_insert(buffer);
10075 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10076 if (gtk_text_iter_backward_cursor_position(&ins))
10077 gtk_text_buffer_place_cursor(buffer, &ins);
10080 static void textview_move_forward_word (GtkTextView *text)
10082 GtkTextBuffer *buffer;
10087 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10089 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10090 mark = gtk_text_buffer_get_insert(buffer);
10091 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10092 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10093 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10094 gtk_text_iter_backward_word_start(&ins);
10095 gtk_text_buffer_place_cursor(buffer, &ins);
10099 static void textview_move_backward_word (GtkTextView *text)
10101 GtkTextBuffer *buffer;
10106 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10108 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10109 mark = gtk_text_buffer_get_insert(buffer);
10110 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10111 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10112 if (gtk_text_iter_backward_word_starts(&ins, 1))
10113 gtk_text_buffer_place_cursor(buffer, &ins);
10116 static void textview_move_end_of_line (GtkTextView *text)
10118 GtkTextBuffer *buffer;
10122 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10124 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10125 mark = gtk_text_buffer_get_insert(buffer);
10126 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10127 if (gtk_text_iter_forward_to_line_end(&ins))
10128 gtk_text_buffer_place_cursor(buffer, &ins);
10131 static void textview_move_next_line (GtkTextView *text)
10133 GtkTextBuffer *buffer;
10138 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10140 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10141 mark = gtk_text_buffer_get_insert(buffer);
10142 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10143 offset = gtk_text_iter_get_line_offset(&ins);
10144 if (gtk_text_iter_forward_line(&ins)) {
10145 gtk_text_iter_set_line_offset(&ins, offset);
10146 gtk_text_buffer_place_cursor(buffer, &ins);
10150 static void textview_move_previous_line (GtkTextView *text)
10152 GtkTextBuffer *buffer;
10157 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10159 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10160 mark = gtk_text_buffer_get_insert(buffer);
10161 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10162 offset = gtk_text_iter_get_line_offset(&ins);
10163 if (gtk_text_iter_backward_line(&ins)) {
10164 gtk_text_iter_set_line_offset(&ins, offset);
10165 gtk_text_buffer_place_cursor(buffer, &ins);
10169 static void textview_delete_forward_character (GtkTextView *text)
10171 GtkTextBuffer *buffer;
10173 GtkTextIter ins, end_iter;
10175 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10177 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10178 mark = gtk_text_buffer_get_insert(buffer);
10179 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10181 if (gtk_text_iter_forward_char(&end_iter)) {
10182 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10186 static void textview_delete_backward_character (GtkTextView *text)
10188 GtkTextBuffer *buffer;
10190 GtkTextIter ins, end_iter;
10192 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10194 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10195 mark = gtk_text_buffer_get_insert(buffer);
10196 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10198 if (gtk_text_iter_backward_char(&end_iter)) {
10199 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10203 static void textview_delete_forward_word (GtkTextView *text)
10205 GtkTextBuffer *buffer;
10207 GtkTextIter ins, end_iter;
10209 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10211 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10212 mark = gtk_text_buffer_get_insert(buffer);
10213 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10215 if (gtk_text_iter_forward_word_end(&end_iter)) {
10216 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10220 static void textview_delete_backward_word (GtkTextView *text)
10222 GtkTextBuffer *buffer;
10224 GtkTextIter ins, end_iter;
10226 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10228 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10229 mark = gtk_text_buffer_get_insert(buffer);
10230 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10232 if (gtk_text_iter_backward_word_start(&end_iter)) {
10233 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10237 static void textview_delete_line (GtkTextView *text)
10239 GtkTextBuffer *buffer;
10241 GtkTextIter ins, start_iter, end_iter;
10243 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10245 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10246 mark = gtk_text_buffer_get_insert(buffer);
10247 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10250 gtk_text_iter_set_line_offset(&start_iter, 0);
10253 if (gtk_text_iter_ends_line(&end_iter)){
10254 if (!gtk_text_iter_forward_char(&end_iter))
10255 gtk_text_iter_backward_char(&start_iter);
10258 gtk_text_iter_forward_to_line_end(&end_iter);
10259 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10262 static void textview_delete_to_line_end (GtkTextView *text)
10264 GtkTextBuffer *buffer;
10266 GtkTextIter ins, end_iter;
10268 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10270 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10271 mark = gtk_text_buffer_get_insert(buffer);
10272 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10274 if (gtk_text_iter_ends_line(&end_iter))
10275 gtk_text_iter_forward_char(&end_iter);
10277 gtk_text_iter_forward_to_line_end(&end_iter);
10278 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10281 #define DO_ACTION(name, act) { \
10282 if(!strcmp(name, a_name)) { \
10286 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10288 const gchar *a_name = gtk_action_get_name(action);
10289 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10290 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10291 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10292 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10293 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10294 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10295 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10296 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10297 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10298 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10299 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10300 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10301 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10302 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10306 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10308 Compose *compose = (Compose *)data;
10309 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10310 ComposeCallAdvancedAction action = -1;
10312 action = compose_call_advanced_action_from_path(gaction);
10315 void (*do_action) (GtkTextView *text);
10316 } action_table[] = {
10317 {textview_move_beginning_of_line},
10318 {textview_move_forward_character},
10319 {textview_move_backward_character},
10320 {textview_move_forward_word},
10321 {textview_move_backward_word},
10322 {textview_move_end_of_line},
10323 {textview_move_next_line},
10324 {textview_move_previous_line},
10325 {textview_delete_forward_character},
10326 {textview_delete_backward_character},
10327 {textview_delete_forward_word},
10328 {textview_delete_backward_word},
10329 {textview_delete_line},
10330 {textview_delete_to_line_end}
10333 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10335 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10336 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10337 if (action_table[action].do_action)
10338 action_table[action].do_action(text);
10340 g_warning("Not implemented yet.");
10344 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10348 if (GTK_IS_EDITABLE(widget)) {
10349 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10350 gtk_editable_set_position(GTK_EDITABLE(widget),
10353 if (widget->parent && widget->parent->parent
10354 && widget->parent->parent->parent) {
10355 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10356 gint y = widget->allocation.y;
10357 gint height = widget->allocation.height;
10358 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10359 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10361 if (y < (int)shown->value) {
10362 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10364 if (y + height > (int)shown->value + (int)shown->page_size) {
10365 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10366 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10367 y + height - (int)shown->page_size - 1);
10369 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10370 (int)shown->upper - (int)shown->page_size - 1);
10377 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10378 compose->focused_editable = widget;
10380 #ifdef GENERIC_UMPC
10381 if (GTK_IS_TEXT_VIEW(widget)
10382 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10383 g_object_ref(compose->notebook);
10384 g_object_ref(compose->edit_vbox);
10385 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10386 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10387 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10388 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10389 g_object_unref(compose->notebook);
10390 g_object_unref(compose->edit_vbox);
10391 g_signal_handlers_block_by_func(G_OBJECT(widget),
10392 G_CALLBACK(compose_grab_focus_cb),
10394 gtk_widget_grab_focus(widget);
10395 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10396 G_CALLBACK(compose_grab_focus_cb),
10398 } else if (!GTK_IS_TEXT_VIEW(widget)
10399 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10400 g_object_ref(compose->notebook);
10401 g_object_ref(compose->edit_vbox);
10402 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10403 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10404 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10405 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10406 g_object_unref(compose->notebook);
10407 g_object_unref(compose->edit_vbox);
10408 g_signal_handlers_block_by_func(G_OBJECT(widget),
10409 G_CALLBACK(compose_grab_focus_cb),
10411 gtk_widget_grab_focus(widget);
10412 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10413 G_CALLBACK(compose_grab_focus_cb),
10419 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10421 compose->modified = TRUE;
10422 // compose_beautify_paragraph(compose, NULL, TRUE);
10423 #ifndef GENERIC_UMPC
10424 compose_set_title(compose);
10428 static void compose_wrap_cb(GtkAction *action, gpointer data)
10430 Compose *compose = (Compose *)data;
10431 compose_beautify_paragraph(compose, NULL, TRUE);
10434 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10436 Compose *compose = (Compose *)data;
10437 compose_wrap_all_full(compose, TRUE);
10440 static void compose_find_cb(GtkAction *action, gpointer data)
10442 Compose *compose = (Compose *)data;
10444 message_search_compose(compose);
10447 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10450 Compose *compose = (Compose *)data;
10451 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10452 if (compose->autowrap)
10453 compose_wrap_all_full(compose, TRUE);
10454 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10457 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10460 Compose *compose = (Compose *)data;
10461 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10464 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10466 Compose *compose = (Compose *)data;
10468 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10471 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10473 Compose *compose = (Compose *)data;
10475 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10478 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10480 g_free(compose->privacy_system);
10482 compose->privacy_system = g_strdup(account->default_privacy_system);
10483 compose_update_privacy_system_menu_item(compose, warn);
10486 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10488 Compose *compose = (Compose *)data;
10490 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10491 gtk_widget_show(compose->ruler_hbox);
10492 prefs_common.show_ruler = TRUE;
10494 gtk_widget_hide(compose->ruler_hbox);
10495 gtk_widget_queue_resize(compose->edit_vbox);
10496 prefs_common.show_ruler = FALSE;
10500 static void compose_attach_drag_received_cb (GtkWidget *widget,
10501 GdkDragContext *context,
10504 GtkSelectionData *data,
10507 gpointer user_data)
10509 Compose *compose = (Compose *)user_data;
10512 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10514 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10516 ) && gtk_drag_get_source_widget(context) !=
10517 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10518 list = uri_list_extract_filenames((const gchar *)data->data);
10519 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10520 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10521 compose_attach_append
10522 (compose, (const gchar *)tmp->data,
10523 utf8_filename, NULL);
10524 g_free(utf8_filename);
10526 if (list) compose_changed_cb(NULL, compose);
10527 list_free_strings(list);
10529 } else if (gtk_drag_get_source_widget(context)
10530 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10531 /* comes from our summaryview */
10532 SummaryView * summaryview = NULL;
10533 GSList * list = NULL, *cur = NULL;
10535 if (mainwindow_get_mainwindow())
10536 summaryview = mainwindow_get_mainwindow()->summaryview;
10539 list = summary_get_selected_msg_list(summaryview);
10541 for (cur = list; cur; cur = cur->next) {
10542 MsgInfo *msginfo = (MsgInfo *)cur->data;
10543 gchar *file = NULL;
10545 file = procmsg_get_message_file_full(msginfo,
10548 compose_attach_append(compose, (const gchar *)file,
10549 (const gchar *)file, "message/rfc822");
10553 g_slist_free(list);
10557 static gboolean compose_drag_drop(GtkWidget *widget,
10558 GdkDragContext *drag_context,
10560 guint time, gpointer user_data)
10562 /* not handling this signal makes compose_insert_drag_received_cb
10567 static void compose_insert_drag_received_cb (GtkWidget *widget,
10568 GdkDragContext *drag_context,
10571 GtkSelectionData *data,
10574 gpointer user_data)
10576 Compose *compose = (Compose *)user_data;
10579 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10582 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10584 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10586 AlertValue val = G_ALERTDEFAULT;
10588 list = uri_list_extract_filenames((const gchar *)data->data);
10589 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10590 /* Assume a list of no files, and data has ://, is a remote link */
10591 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10592 gchar *tmpfile = get_tmp_file();
10593 str_write_to_file(tmpdata, tmpfile);
10595 compose_insert_file(compose, tmpfile);
10596 claws_unlink(tmpfile);
10598 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10599 compose_beautify_paragraph(compose, NULL, TRUE);
10602 switch (prefs_common.compose_dnd_mode) {
10603 case COMPOSE_DND_ASK:
10604 val = alertpanel_full(_("Insert or attach?"),
10605 _("Do you want to insert the contents of the file(s) "
10606 "into the message body, or attach it to the email?"),
10607 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10608 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10610 case COMPOSE_DND_INSERT:
10611 val = G_ALERTALTERNATE;
10613 case COMPOSE_DND_ATTACH:
10614 val = G_ALERTOTHER;
10617 /* unexpected case */
10618 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10621 if (val & G_ALERTDISABLE) {
10622 val &= ~G_ALERTDISABLE;
10623 /* remember what action to perform by default, only if we don't click Cancel */
10624 if (val == G_ALERTALTERNATE)
10625 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10626 else if (val == G_ALERTOTHER)
10627 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10630 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10631 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10632 list_free_strings(list);
10635 } else if (val == G_ALERTOTHER) {
10636 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10637 list_free_strings(list);
10642 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10643 compose_insert_file(compose, (const gchar *)tmp->data);
10645 list_free_strings(list);
10647 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10650 #if GTK_CHECK_VERSION(2, 8, 0)
10651 /* do nothing, handled by GTK */
10653 gchar *tmpfile = get_tmp_file();
10654 str_write_to_file((const gchar *)data->data, tmpfile);
10655 compose_insert_file(compose, tmpfile);
10656 claws_unlink(tmpfile);
10658 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10662 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10665 static void compose_header_drag_received_cb (GtkWidget *widget,
10666 GdkDragContext *drag_context,
10669 GtkSelectionData *data,
10672 gpointer user_data)
10674 GtkEditable *entry = (GtkEditable *)user_data;
10675 gchar *email = (gchar *)data->data;
10677 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10680 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10681 gchar *decoded=g_new(gchar, strlen(email));
10684 email += strlen("mailto:");
10685 decode_uri(decoded, email); /* will fit */
10686 gtk_editable_delete_text(entry, 0, -1);
10687 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10688 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10692 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10695 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10697 Compose *compose = (Compose *)data;
10699 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10700 compose->return_receipt = TRUE;
10702 compose->return_receipt = FALSE;
10705 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10707 Compose *compose = (Compose *)data;
10709 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10710 compose->remove_references = TRUE;
10712 compose->remove_references = FALSE;
10715 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10716 ComposeHeaderEntry *headerentry)
10718 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10722 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10723 GdkEventKey *event,
10724 ComposeHeaderEntry *headerentry)
10726 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10727 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10728 !(event->state & GDK_MODIFIER_MASK) &&
10729 (event->keyval == GDK_BackSpace) &&
10730 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10731 gtk_container_remove
10732 (GTK_CONTAINER(headerentry->compose->header_table),
10733 headerentry->combo);
10734 gtk_container_remove
10735 (GTK_CONTAINER(headerentry->compose->header_table),
10736 headerentry->entry);
10737 headerentry->compose->header_list =
10738 g_slist_remove(headerentry->compose->header_list,
10740 g_free(headerentry);
10741 } else if (event->keyval == GDK_Tab) {
10742 if (headerentry->compose->header_last == headerentry) {
10743 /* Override default next focus, and give it to subject_entry
10744 * instead of notebook tabs
10746 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10747 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10754 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10755 ComposeHeaderEntry *headerentry)
10757 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10758 compose_create_header_entry(headerentry->compose);
10759 g_signal_handlers_disconnect_matched
10760 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10761 0, 0, NULL, NULL, headerentry);
10763 /* Automatically scroll down */
10764 GTK_EVENTS_FLUSH();
10765 compose_show_first_last_header(headerentry->compose, FALSE);
10771 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10773 GtkAdjustment *vadj;
10775 cm_return_if_fail(compose);
10776 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10777 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10778 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10779 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10780 gtk_adjustment_changed(vadj);
10783 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10784 const gchar *text, gint len, Compose *compose)
10786 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10787 (G_OBJECT(compose->text), "paste_as_quotation"));
10790 cm_return_if_fail(text != NULL);
10792 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10793 G_CALLBACK(text_inserted),
10795 if (paste_as_quotation) {
10797 const gchar *qmark;
10799 GtkTextIter start_iter;
10802 len = strlen(text);
10804 new_text = g_strndup(text, len);
10806 qmark = compose_quote_char_from_context(compose);
10808 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10809 gtk_text_buffer_place_cursor(buffer, iter);
10811 pos = gtk_text_iter_get_offset(iter);
10813 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10814 _("Quote format error at line %d."));
10815 quote_fmt_reset_vartable();
10817 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10818 GINT_TO_POINTER(paste_as_quotation - 1));
10820 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10821 gtk_text_buffer_place_cursor(buffer, iter);
10822 gtk_text_buffer_delete_mark(buffer, mark);
10824 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10825 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10826 compose_beautify_paragraph(compose, &start_iter, FALSE);
10827 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10828 gtk_text_buffer_delete_mark(buffer, mark);
10830 if (strcmp(text, "\n") || compose->automatic_break
10831 || gtk_text_iter_starts_line(iter)) {
10832 GtkTextIter before_ins;
10833 gtk_text_buffer_insert(buffer, iter, text, len);
10834 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10835 before_ins = *iter;
10836 gtk_text_iter_backward_chars(&before_ins, len);
10837 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10840 /* check if the preceding is just whitespace or quote */
10841 GtkTextIter start_line;
10842 gchar *tmp = NULL, *quote = NULL;
10843 gint quote_len = 0, is_normal = 0;
10844 start_line = *iter;
10845 gtk_text_iter_set_line_offset(&start_line, 0);
10846 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10849 if (*tmp == '\0') {
10852 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10860 gtk_text_buffer_insert(buffer, iter, text, len);
10862 gtk_text_buffer_insert_with_tags_by_name(buffer,
10863 iter, text, len, "no_join", NULL);
10868 if (!paste_as_quotation) {
10869 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10870 compose_beautify_paragraph(compose, iter, FALSE);
10871 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10872 gtk_text_buffer_delete_mark(buffer, mark);
10875 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10876 G_CALLBACK(text_inserted),
10878 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10880 if (prefs_common.autosave &&
10881 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10882 compose->draft_timeout_tag != -2 /* disabled while loading */)
10883 compose->draft_timeout_tag = g_timeout_add
10884 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10886 static gint compose_defer_auto_save_draft(Compose *compose)
10888 compose->draft_timeout_tag = -1;
10889 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10894 static void compose_check_all(GtkAction *action, gpointer data)
10896 Compose *compose = (Compose *)data;
10897 if (!compose->gtkaspell)
10900 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10901 claws_spell_entry_check_all(
10902 CLAWS_SPELL_ENTRY(compose->subject_entry));
10904 gtkaspell_check_all(compose->gtkaspell);
10907 static void compose_highlight_all(GtkAction *action, gpointer data)
10909 Compose *compose = (Compose *)data;
10910 if (compose->gtkaspell) {
10911 claws_spell_entry_recheck_all(
10912 CLAWS_SPELL_ENTRY(compose->subject_entry));
10913 gtkaspell_highlight_all(compose->gtkaspell);
10917 static void compose_check_backwards(GtkAction *action, gpointer data)
10919 Compose *compose = (Compose *)data;
10920 if (!compose->gtkaspell) {
10921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10925 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10926 claws_spell_entry_check_backwards(
10927 CLAWS_SPELL_ENTRY(compose->subject_entry));
10929 gtkaspell_check_backwards(compose->gtkaspell);
10932 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10934 Compose *compose = (Compose *)data;
10935 if (!compose->gtkaspell) {
10936 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10940 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10941 claws_spell_entry_check_forwards_go(
10942 CLAWS_SPELL_ENTRY(compose->subject_entry));
10944 gtkaspell_check_forwards_go(compose->gtkaspell);
10949 *\brief Guess originating forward account from MsgInfo and several
10950 * "common preference" settings. Return NULL if no guess.
10952 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10954 PrefsAccount *account = NULL;
10956 cm_return_val_if_fail(msginfo, NULL);
10957 cm_return_val_if_fail(msginfo->folder, NULL);
10958 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10960 if (msginfo->folder->prefs->enable_default_account)
10961 account = account_find_from_id(msginfo->folder->prefs->default_account);
10964 account = msginfo->folder->folder->account;
10966 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10968 Xstrdup_a(to, msginfo->to, return NULL);
10969 extract_address(to);
10970 account = account_find_from_address(to, FALSE);
10973 if (!account && prefs_common.forward_account_autosel) {
10974 gchar cc[BUFFSIZE];
10975 if (!procheader_get_header_from_msginfo
10976 (msginfo, cc,sizeof cc , "Cc:")) {
10977 gchar *buf = cc + strlen("Cc:");
10978 extract_address(buf);
10979 account = account_find_from_address(buf, FALSE);
10983 if (!account && prefs_common.forward_account_autosel) {
10984 gchar deliveredto[BUFFSIZE];
10985 if (!procheader_get_header_from_msginfo
10986 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10987 gchar *buf = deliveredto + strlen("Delivered-To:");
10988 extract_address(buf);
10989 account = account_find_from_address(buf, FALSE);
10996 gboolean compose_close(Compose *compose)
11000 if (!g_mutex_trylock(compose->mutex)) {
11001 /* we have to wait for the (possibly deferred by auto-save)
11002 * drafting to be done, before destroying the compose under
11004 debug_print("waiting for drafting to finish...\n");
11005 compose_allow_user_actions(compose, FALSE);
11006 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11009 cm_return_val_if_fail(compose, FALSE);
11010 gtkut_widget_get_uposition(compose->window, &x, &y);
11011 if (!compose->batch) {
11012 prefs_common.compose_x = x;
11013 prefs_common.compose_y = y;
11015 g_mutex_unlock(compose->mutex);
11016 compose_destroy(compose);
11021 * Add entry field for each address in list.
11022 * \param compose E-Mail composition object.
11023 * \param listAddress List of (formatted) E-Mail addresses.
11025 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11028 node = listAddress;
11030 addr = ( gchar * ) node->data;
11031 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11032 node = g_list_next( node );
11036 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11037 guint action, gboolean opening_multiple)
11039 gchar *body = NULL;
11040 GSList *new_msglist = NULL;
11041 MsgInfo *tmp_msginfo = NULL;
11042 gboolean originally_enc = FALSE;
11043 gboolean originally_sig = FALSE;
11044 Compose *compose = NULL;
11045 gchar *s_system = NULL;
11047 cm_return_if_fail(msgview != NULL);
11049 cm_return_if_fail(msginfo_list != NULL);
11051 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11052 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11053 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11055 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11056 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11057 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11058 orig_msginfo, mimeinfo);
11059 if (tmp_msginfo != NULL) {
11060 new_msglist = g_slist_append(NULL, tmp_msginfo);
11062 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11063 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11064 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11066 tmp_msginfo->folder = orig_msginfo->folder;
11067 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11068 if (orig_msginfo->tags) {
11069 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11070 tmp_msginfo->folder->tags_dirty = TRUE;
11076 if (!opening_multiple)
11077 body = messageview_get_selection(msgview);
11080 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11081 procmsg_msginfo_free(tmp_msginfo);
11082 g_slist_free(new_msglist);
11084 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11086 if (compose && originally_enc) {
11087 compose_force_encryption(compose, compose->account, FALSE, s_system);
11090 if (compose && originally_sig && compose->account->default_sign_reply) {
11091 compose_force_signing(compose, compose->account, s_system);
11095 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11098 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11101 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11102 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11103 GSList *cur = msginfo_list;
11104 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11105 "messages. Opening the windows "
11106 "could take some time. Do you "
11107 "want to continue?"),
11108 g_slist_length(msginfo_list));
11109 if (g_slist_length(msginfo_list) > 9
11110 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11111 != G_ALERTALTERNATE) {
11116 /* We'll open multiple compose windows */
11117 /* let the WM place the next windows */
11118 compose_force_window_origin = FALSE;
11119 for (; cur; cur = cur->next) {
11121 tmplist.data = cur->data;
11122 tmplist.next = NULL;
11123 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11125 compose_force_window_origin = TRUE;
11127 /* forwarding multiple mails as attachments is done via a
11128 * single compose window */
11129 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11133 void compose_set_position(Compose *compose, gint pos)
11135 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11137 gtkut_text_view_set_position(text, pos);
11140 gboolean compose_search_string(Compose *compose,
11141 const gchar *str, gboolean case_sens)
11143 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11145 return gtkut_text_view_search_string(text, str, case_sens);
11148 gboolean compose_search_string_backward(Compose *compose,
11149 const gchar *str, gboolean case_sens)
11151 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11153 return gtkut_text_view_search_string_backward(text, str, case_sens);
11156 /* allocate a msginfo structure and populate its data from a compose data structure */
11157 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11159 MsgInfo *newmsginfo;
11161 gchar buf[BUFFSIZE];
11163 cm_return_val_if_fail( compose != NULL, NULL );
11165 newmsginfo = procmsg_msginfo_new();
11168 get_rfc822_date(buf, sizeof(buf));
11169 newmsginfo->date = g_strdup(buf);
11172 if (compose->from_name) {
11173 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11174 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11178 if (compose->subject_entry)
11179 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11181 /* to, cc, reply-to, newsgroups */
11182 for (list = compose->header_list; list; list = list->next) {
11183 gchar *header = gtk_editable_get_chars(
11185 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11186 gchar *entry = gtk_editable_get_chars(
11187 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11189 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11190 if ( newmsginfo->to == NULL ) {
11191 newmsginfo->to = g_strdup(entry);
11192 } else if (entry && *entry) {
11193 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11194 g_free(newmsginfo->to);
11195 newmsginfo->to = tmp;
11198 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11199 if ( newmsginfo->cc == NULL ) {
11200 newmsginfo->cc = g_strdup(entry);
11201 } else if (entry && *entry) {
11202 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11203 g_free(newmsginfo->cc);
11204 newmsginfo->cc = tmp;
11207 if ( strcasecmp(header,
11208 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11209 if ( newmsginfo->newsgroups == NULL ) {
11210 newmsginfo->newsgroups = g_strdup(entry);
11211 } else if (entry && *entry) {
11212 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11213 g_free(newmsginfo->newsgroups);
11214 newmsginfo->newsgroups = tmp;
11222 /* other data is unset */
11228 /* update compose's dictionaries from folder dict settings */
11229 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11230 FolderItem *folder_item)
11232 cm_return_if_fail(compose != NULL);
11234 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11235 FolderItemPrefs *prefs = folder_item->prefs;
11237 if (prefs->enable_default_dictionary)
11238 gtkaspell_change_dict(compose->gtkaspell,
11239 prefs->default_dictionary, FALSE);
11240 if (folder_item->prefs->enable_default_alt_dictionary)
11241 gtkaspell_change_alt_dict(compose->gtkaspell,
11242 prefs->default_alt_dictionary);
11243 if (prefs->enable_default_dictionary
11244 || prefs->enable_default_alt_dictionary)
11245 compose_spell_menu_changed(compose);