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);
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:");
2492 header = prefs_common_translated_header_name(header);
2494 cur = begin = (gchar *)address;
2496 /* we separate the line by commas, but not if we're inside a quoted
2498 while (*cur != '\0') {
2500 in_quote = !in_quote;
2501 if (*cur == ',' && !in_quote) {
2502 gchar *tmp = g_strdup(begin);
2504 tmp[cur-begin]='\0';
2507 while (*tmp == ' ' || *tmp == '\t')
2509 compose_add_header_entry(compose, header, tmp, pref_type);
2516 gchar *tmp = g_strdup(begin);
2518 tmp[cur-begin]='\0';
2521 while (*tmp == ' ' || *tmp == '\t')
2523 compose_add_header_entry(compose, header, tmp, pref_type);
2528 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2530 static GdkColor yellow;
2531 static GdkColor black;
2532 static gboolean yellow_initialised = FALSE;
2536 if (!yellow_initialised) {
2537 gdk_color_parse("#f5f6be", &yellow);
2538 gdk_color_parse("#000000", &black);
2539 yellow_initialised = gdk_colormap_alloc_color(
2540 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2541 yellow_initialised &= gdk_colormap_alloc_color(
2542 gdk_colormap_get_system(), &black, FALSE, TRUE);
2545 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2546 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2547 if (gtk_entry_get_text(entry) &&
2548 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2549 if (yellow_initialised) {
2550 gtk_widget_modify_base(
2551 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2552 GTK_STATE_NORMAL, &yellow);
2553 gtk_widget_modify_text(
2554 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2555 GTK_STATE_NORMAL, &black);
2561 void compose_toolbar_cb(gint action, gpointer data)
2563 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2564 Compose *compose = (Compose*)toolbar_item->parent;
2566 cm_return_if_fail(compose != NULL);
2570 compose_send_cb(NULL, compose);
2573 compose_send_later_cb(NULL, compose);
2576 compose_draft(compose, COMPOSE_QUIT_EDITING);
2579 compose_insert_file_cb(NULL, compose);
2582 compose_attach_cb(NULL, compose);
2585 compose_insert_sig(compose, FALSE);
2588 compose_ext_editor_cb(NULL, compose);
2590 case A_LINEWRAP_CURRENT:
2591 compose_beautify_paragraph(compose, NULL, TRUE);
2593 case A_LINEWRAP_ALL:
2594 compose_wrap_all_full(compose, TRUE);
2597 compose_address_cb(NULL, compose);
2600 case A_CHECK_SPELLING:
2601 compose_check_all(NULL, compose);
2609 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2614 gchar *subject = NULL;
2618 gchar **attach = NULL;
2619 MailField mfield = NO_FIELD_PRESENT;
2621 /* get mailto parts but skip from */
2622 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2625 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2626 mfield = TO_FIELD_PRESENT;
2629 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2631 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2633 if (!g_utf8_validate (subject, -1, NULL)) {
2634 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2635 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2638 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2640 mfield = SUBJECT_FIELD_PRESENT;
2643 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2644 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2647 gboolean prev_autowrap = compose->autowrap;
2649 compose->autowrap = FALSE;
2651 mark = gtk_text_buffer_get_insert(buffer);
2652 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2654 if (!g_utf8_validate (body, -1, NULL)) {
2655 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2656 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2659 gtk_text_buffer_insert(buffer, &iter, body, -1);
2661 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2663 compose->autowrap = prev_autowrap;
2664 if (compose->autowrap)
2665 compose_wrap_all(compose);
2666 mfield = BODY_FIELD_PRESENT;
2670 gint i = 0, att = 0;
2671 gchar *warn_files = NULL;
2672 while (attach[i] != NULL) {
2673 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2674 if (utf8_filename) {
2675 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2676 gchar *tmp = g_strdup_printf("%s%s\n",
2677 warn_files?warn_files:"",
2683 g_free(utf8_filename);
2685 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2690 alertpanel_notice(ngettext(
2691 "The following file has been attached: \n%s",
2692 "The following files have been attached: \n%s", att), warn_files);
2706 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2708 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2709 {"Cc:", NULL, TRUE},
2710 {"References:", NULL, FALSE},
2711 {"Bcc:", NULL, TRUE},
2712 {"Newsgroups:", NULL, TRUE},
2713 {"Followup-To:", NULL, TRUE},
2714 {"List-Post:", NULL, FALSE},
2715 {"X-Priority:", NULL, FALSE},
2716 {NULL, NULL, FALSE}};
2732 cm_return_val_if_fail(msginfo != NULL, -1);
2734 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2735 procheader_get_header_fields(fp, hentry);
2738 if (hentry[H_REPLY_TO].body != NULL) {
2739 if (hentry[H_REPLY_TO].body[0] != '\0') {
2741 conv_unmime_header(hentry[H_REPLY_TO].body,
2744 g_free(hentry[H_REPLY_TO].body);
2745 hentry[H_REPLY_TO].body = NULL;
2747 if (hentry[H_CC].body != NULL) {
2748 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2749 g_free(hentry[H_CC].body);
2750 hentry[H_CC].body = NULL;
2752 if (hentry[H_REFERENCES].body != NULL) {
2753 if (compose->mode == COMPOSE_REEDIT)
2754 compose->references = hentry[H_REFERENCES].body;
2756 compose->references = compose_parse_references
2757 (hentry[H_REFERENCES].body, msginfo->msgid);
2758 g_free(hentry[H_REFERENCES].body);
2760 hentry[H_REFERENCES].body = NULL;
2762 if (hentry[H_BCC].body != NULL) {
2763 if (compose->mode == COMPOSE_REEDIT)
2765 conv_unmime_header(hentry[H_BCC].body, NULL);
2766 g_free(hentry[H_BCC].body);
2767 hentry[H_BCC].body = NULL;
2769 if (hentry[H_NEWSGROUPS].body != NULL) {
2770 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2771 hentry[H_NEWSGROUPS].body = NULL;
2773 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2774 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2775 compose->followup_to =
2776 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2779 g_free(hentry[H_FOLLOWUP_TO].body);
2780 hentry[H_FOLLOWUP_TO].body = NULL;
2782 if (hentry[H_LIST_POST].body != NULL) {
2783 gchar *to = NULL, *start = NULL;
2785 extract_address(hentry[H_LIST_POST].body);
2786 if (hentry[H_LIST_POST].body[0] != '\0') {
2787 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2789 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2790 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2793 g_free(compose->ml_post);
2794 compose->ml_post = to;
2797 g_free(hentry[H_LIST_POST].body);
2798 hentry[H_LIST_POST].body = NULL;
2801 /* CLAWS - X-Priority */
2802 if (compose->mode == COMPOSE_REEDIT)
2803 if (hentry[H_X_PRIORITY].body != NULL) {
2806 priority = atoi(hentry[H_X_PRIORITY].body);
2807 g_free(hentry[H_X_PRIORITY].body);
2809 hentry[H_X_PRIORITY].body = NULL;
2811 if (priority < PRIORITY_HIGHEST ||
2812 priority > PRIORITY_LOWEST)
2813 priority = PRIORITY_NORMAL;
2815 compose->priority = priority;
2818 if (compose->mode == COMPOSE_REEDIT) {
2819 if (msginfo->inreplyto && *msginfo->inreplyto)
2820 compose->inreplyto = g_strdup(msginfo->inreplyto);
2824 if (msginfo->msgid && *msginfo->msgid)
2825 compose->inreplyto = g_strdup(msginfo->msgid);
2827 if (!compose->references) {
2828 if (msginfo->msgid && *msginfo->msgid) {
2829 if (msginfo->inreplyto && *msginfo->inreplyto)
2830 compose->references =
2831 g_strdup_printf("<%s>\n\t<%s>",
2835 compose->references =
2836 g_strconcat("<", msginfo->msgid, ">",
2838 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2839 compose->references =
2840 g_strconcat("<", msginfo->inreplyto, ">",
2848 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2850 GSList *ref_id_list, *cur;
2854 ref_id_list = references_list_append(NULL, ref);
2855 if (!ref_id_list) return NULL;
2856 if (msgid && *msgid)
2857 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2862 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2863 /* "<" + Message-ID + ">" + CR+LF+TAB */
2864 len += strlen((gchar *)cur->data) + 5;
2866 if (len > MAX_REFERENCES_LEN) {
2867 /* remove second message-ID */
2868 if (ref_id_list && ref_id_list->next &&
2869 ref_id_list->next->next) {
2870 g_free(ref_id_list->next->data);
2871 ref_id_list = g_slist_remove
2872 (ref_id_list, ref_id_list->next->data);
2874 slist_free_strings(ref_id_list);
2875 g_slist_free(ref_id_list);
2882 new_ref = g_string_new("");
2883 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2884 if (new_ref->len > 0)
2885 g_string_append(new_ref, "\n\t");
2886 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2889 slist_free_strings(ref_id_list);
2890 g_slist_free(ref_id_list);
2892 new_ref_str = new_ref->str;
2893 g_string_free(new_ref, FALSE);
2898 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2899 const gchar *fmt, const gchar *qmark,
2900 const gchar *body, gboolean rewrap,
2901 gboolean need_unescape,
2902 const gchar *err_msg)
2904 MsgInfo* dummyinfo = NULL;
2905 gchar *quote_str = NULL;
2907 gboolean prev_autowrap;
2908 const gchar *trimmed_body = body;
2909 gint cursor_pos = -1;
2910 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2911 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2916 SIGNAL_BLOCK(buffer);
2919 dummyinfo = compose_msginfo_new_from_compose(compose);
2920 msginfo = dummyinfo;
2923 if (qmark != NULL) {
2925 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2926 compose->gtkaspell);
2928 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2930 quote_fmt_scan_string(qmark);
2933 buf = quote_fmt_get_buffer();
2935 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2937 Xstrdup_a(quote_str, buf, goto error)
2940 if (fmt && *fmt != '\0') {
2943 while (*trimmed_body == '\n')
2947 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2948 compose->gtkaspell);
2950 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2952 if (need_unescape) {
2955 /* decode \-escape sequences in the internal representation of the quote format */
2956 tmp = malloc(strlen(fmt)+1);
2957 pref_get_unescaped_pref(tmp, fmt);
2958 quote_fmt_scan_string(tmp);
2962 quote_fmt_scan_string(fmt);
2966 buf = quote_fmt_get_buffer();
2968 gint line = quote_fmt_get_line();
2969 alertpanel_error(err_msg, line);
2975 prev_autowrap = compose->autowrap;
2976 compose->autowrap = FALSE;
2978 mark = gtk_text_buffer_get_insert(buffer);
2979 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2980 if (g_utf8_validate(buf, -1, NULL)) {
2981 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2983 gchar *tmpout = NULL;
2984 tmpout = conv_codeset_strdup
2985 (buf, conv_get_locale_charset_str_no_utf8(),
2987 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2989 tmpout = g_malloc(strlen(buf)*2+1);
2990 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2992 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2996 cursor_pos = quote_fmt_get_cursor_pos();
2997 if (cursor_pos == -1)
2998 cursor_pos = gtk_text_iter_get_offset(&iter);
2999 compose->set_cursor_pos = cursor_pos;
3001 gtk_text_buffer_get_start_iter(buffer, &iter);
3002 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3003 gtk_text_buffer_place_cursor(buffer, &iter);
3005 compose->autowrap = prev_autowrap;
3006 if (compose->autowrap && rewrap)
3007 compose_wrap_all(compose);
3014 SIGNAL_UNBLOCK(buffer);
3016 procmsg_msginfo_free( dummyinfo );
3021 /* if ml_post is of type addr@host and from is of type
3022 * addr-anything@host, return TRUE
3024 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3026 gchar *left_ml = NULL;
3027 gchar *right_ml = NULL;
3028 gchar *left_from = NULL;
3029 gchar *right_from = NULL;
3030 gboolean result = FALSE;
3032 if (!ml_post || !from)
3035 left_ml = g_strdup(ml_post);
3036 if (strstr(left_ml, "@")) {
3037 right_ml = strstr(left_ml, "@")+1;
3038 *(strstr(left_ml, "@")) = '\0';
3041 left_from = g_strdup(from);
3042 if (strstr(left_from, "@")) {
3043 right_from = strstr(left_from, "@")+1;
3044 *(strstr(left_from, "@")) = '\0';
3047 if (left_ml && left_from && right_ml && right_from
3048 && !strncmp(left_from, left_ml, strlen(left_ml))
3049 && !strcmp(right_from, right_ml)) {
3058 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3059 gboolean to_all, gboolean to_ml,
3061 gboolean followup_and_reply_to)
3063 GSList *cc_list = NULL;
3066 gchar *replyto = NULL;
3067 gchar *ac_email = NULL;
3069 gboolean reply_to_ml = FALSE;
3070 gboolean default_reply_to = FALSE;
3072 cm_return_if_fail(compose->account != NULL);
3073 cm_return_if_fail(msginfo != NULL);
3075 reply_to_ml = to_ml && compose->ml_post;
3077 default_reply_to = msginfo->folder &&
3078 msginfo->folder->prefs->enable_default_reply_to;
3080 if (compose->account->protocol != A_NNTP) {
3081 if (msginfo && msginfo->folder && msginfo->folder->prefs) {
3082 if (msginfo->folder->prefs->enable_default_replyto) {
3083 compose_entry_append(compose, msginfo->folder->prefs->default_replyto,
3084 COMPOSE_REPLYTO, PREF_FOLDER);
3086 if (msginfo->folder->prefs->enable_default_bcc) {
3087 compose_entry_append(compose, msginfo->folder->prefs->default_bcc,
3088 COMPOSE_BCC, PREF_FOLDER);
3090 if (msginfo->folder->prefs->enable_default_cc) {
3091 compose_entry_append(compose, msginfo->folder->prefs->default_cc,
3092 COMPOSE_CC, PREF_FOLDER);
3095 if (reply_to_ml && !default_reply_to) {
3097 gboolean is_subscr = is_subscription(compose->ml_post,
3100 /* normal answer to ml post with a reply-to */
3101 compose_entry_append(compose,
3103 COMPOSE_TO, PREF_ML);
3104 if (compose->replyto)
3105 compose_entry_append(compose,
3107 COMPOSE_CC, PREF_ML);
3109 /* answer to subscription confirmation */
3110 if (compose->replyto)
3111 compose_entry_append(compose,
3113 COMPOSE_TO, PREF_ML);
3114 else if (msginfo->from)
3115 compose_entry_append(compose,
3117 COMPOSE_TO, PREF_ML);
3120 else if (!(to_all || to_sender) && default_reply_to) {
3121 compose_entry_append(compose,
3122 msginfo->folder->prefs->default_reply_to,
3123 COMPOSE_TO, PREF_FOLDER);
3124 compose_entry_mark_default_to(compose,
3125 msginfo->folder->prefs->default_reply_to);
3130 Xstrdup_a(tmp1, msginfo->from, return);
3131 extract_address(tmp1);
3132 if (to_all || to_sender ||
3133 !account_find_from_address(tmp1, FALSE))
3134 compose_entry_append(compose,
3135 (compose->replyto && !to_sender)
3136 ? compose->replyto :
3137 msginfo->from ? msginfo->from : "",
3138 COMPOSE_TO, PREF_NONE);
3139 else if (!to_all && !to_sender) {
3140 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3141 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3142 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3143 if (compose->replyto) {
3144 compose_entry_append(compose,
3146 COMPOSE_TO, PREF_NONE);
3148 compose_entry_append(compose,
3149 msginfo->from ? msginfo->from : "",
3150 COMPOSE_TO, PREF_NONE);
3153 /* replying to own mail, use original recp */
3154 compose_entry_append(compose,
3155 msginfo->to ? msginfo->to : "",
3156 COMPOSE_TO, PREF_NONE);
3157 compose_entry_append(compose,
3158 msginfo->cc ? msginfo->cc : "",
3159 COMPOSE_CC, PREF_NONE);
3164 if (to_sender || (compose->followup_to &&
3165 !strncmp(compose->followup_to, "poster", 6)))
3166 compose_entry_append
3168 (compose->replyto ? compose->replyto :
3169 msginfo->from ? msginfo->from : ""),
3170 COMPOSE_TO, PREF_NONE);
3172 else if (followup_and_reply_to || to_all) {
3173 compose_entry_append
3175 (compose->replyto ? compose->replyto :
3176 msginfo->from ? msginfo->from : ""),
3177 COMPOSE_TO, PREF_NONE);
3179 compose_entry_append
3181 compose->followup_to ? compose->followup_to :
3182 compose->newsgroups ? compose->newsgroups : "",
3183 COMPOSE_NEWSGROUPS, PREF_NONE);
3186 compose_entry_append
3188 compose->followup_to ? compose->followup_to :
3189 compose->newsgroups ? compose->newsgroups : "",
3190 COMPOSE_NEWSGROUPS, PREF_NONE);
3193 if (msginfo->subject && *msginfo->subject) {
3197 buf = p = g_strdup(msginfo->subject);
3198 p += subject_get_prefix_length(p);
3199 memmove(buf, p, strlen(p) + 1);
3201 buf2 = g_strdup_printf("Re: %s", buf);
3202 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3207 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3209 if (to_ml && compose->ml_post) return;
3210 if (!to_all || compose->account->protocol == A_NNTP) return;
3212 if (compose->replyto) {
3213 Xstrdup_a(replyto, compose->replyto, return);
3214 extract_address(replyto);
3216 if (msginfo->from) {
3217 Xstrdup_a(from, msginfo->from, return);
3218 extract_address(from);
3221 if (replyto && from)
3222 cc_list = address_list_append_with_comments(cc_list, from);
3223 if (to_all && msginfo->folder &&
3224 msginfo->folder->prefs->enable_default_reply_to)
3225 cc_list = address_list_append_with_comments(cc_list,
3226 msginfo->folder->prefs->default_reply_to);
3227 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3228 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3230 ac_email = g_utf8_strdown(compose->account->address, -1);
3233 for (cur = cc_list; cur != NULL; cur = cur->next) {
3234 gchar *addr = g_utf8_strdown(cur->data, -1);
3235 extract_address(addr);
3237 if (strcmp(ac_email, addr))
3238 compose_entry_append(compose, (gchar *)cur->data,
3239 COMPOSE_CC, PREF_NONE);
3241 debug_print("Cc address same as compose account's, ignoring\n");
3246 slist_free_strings(cc_list);
3247 g_slist_free(cc_list);
3253 #define SET_ENTRY(entry, str) \
3256 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3259 #define SET_ADDRESS(type, str) \
3262 compose_entry_append(compose, str, type, PREF_NONE); \
3265 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3267 cm_return_if_fail(msginfo != NULL);
3269 SET_ENTRY(subject_entry, msginfo->subject);
3270 SET_ENTRY(from_name, msginfo->from);
3271 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3272 SET_ADDRESS(COMPOSE_CC, compose->cc);
3273 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3274 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3275 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3276 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3278 compose_update_priority_menu_item(compose);
3279 compose_update_privacy_system_menu_item(compose, FALSE);
3280 compose_show_first_last_header(compose, TRUE);
3286 static void compose_insert_sig(Compose *compose, gboolean replace)
3288 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3289 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3291 GtkTextIter iter, iter_end;
3292 gint cur_pos, ins_pos;
3293 gboolean prev_autowrap;
3294 gboolean found = FALSE;
3295 gboolean exists = FALSE;
3297 cm_return_if_fail(compose->account != NULL);
3301 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3302 G_CALLBACK(compose_changed_cb),
3305 mark = gtk_text_buffer_get_insert(buffer);
3306 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3307 cur_pos = gtk_text_iter_get_offset (&iter);
3310 gtk_text_buffer_get_end_iter(buffer, &iter);
3312 exists = (compose->sig_str != NULL);
3315 GtkTextIter first_iter, start_iter, end_iter;
3317 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3319 if (!exists || compose->sig_str[0] == '\0')
3322 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3323 compose->signature_tag);
3326 /* include previous \n\n */
3327 gtk_text_iter_backward_chars(&first_iter, 1);
3328 start_iter = first_iter;
3329 end_iter = first_iter;
3331 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3332 compose->signature_tag);
3333 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3334 compose->signature_tag);
3336 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3342 g_free(compose->sig_str);
3343 compose->sig_str = account_get_signature_str(compose->account);
3345 cur_pos = gtk_text_iter_get_offset(&iter);
3347 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3348 g_free(compose->sig_str);
3349 compose->sig_str = NULL;
3351 if (compose->sig_inserted == FALSE)
3352 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3353 compose->sig_inserted = TRUE;
3355 cur_pos = gtk_text_iter_get_offset(&iter);
3356 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3358 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3359 gtk_text_iter_forward_chars(&iter, 1);
3360 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3361 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3363 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3364 cur_pos = gtk_text_buffer_get_char_count (buffer);
3367 /* put the cursor where it should be
3368 * either where the quote_fmt says, either where it was */
3369 if (compose->set_cursor_pos < 0)
3370 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3372 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3373 compose->set_cursor_pos);
3375 compose->set_cursor_pos = -1;
3376 gtk_text_buffer_place_cursor(buffer, &iter);
3377 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3378 G_CALLBACK(compose_changed_cb),
3384 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3387 GtkTextBuffer *buffer;
3390 const gchar *cur_encoding;
3391 gchar buf[BUFFSIZE];
3394 gboolean prev_autowrap;
3395 gboolean badtxt = FALSE;
3396 struct stat file_stat;
3399 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3401 /* get the size of the file we are about to insert */
3402 ret = g_stat(file, &file_stat);
3404 gchar *shortfile = g_path_get_basename(file);
3405 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3407 return COMPOSE_INSERT_NO_FILE;
3408 } else if (prefs_common.warn_large_insert == TRUE) {
3410 /* ask user for confirmation if the file is large */
3411 if (prefs_common.warn_large_insert_size < 0 ||
3412 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3416 msg = g_strdup_printf(_("You are about to insert a file of %s "
3417 "in the message body. Are you sure you want to do that?"),
3418 to_human_readable(file_stat.st_size));
3419 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3420 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3423 /* do we ask for confirmation next time? */
3424 if (aval & G_ALERTDISABLE) {
3425 /* no confirmation next time, disable feature in preferences */
3426 aval &= ~G_ALERTDISABLE;
3427 prefs_common.warn_large_insert = FALSE;
3430 /* abort file insertion if user canceled action */
3431 if (aval != G_ALERTALTERNATE) {
3432 return COMPOSE_INSERT_NO_FILE;
3438 if ((fp = g_fopen(file, "rb")) == NULL) {
3439 FILE_OP_ERROR(file, "fopen");
3440 return COMPOSE_INSERT_READ_ERROR;
3443 prev_autowrap = compose->autowrap;
3444 compose->autowrap = FALSE;
3446 text = GTK_TEXT_VIEW(compose->text);
3447 buffer = gtk_text_view_get_buffer(text);
3448 mark = gtk_text_buffer_get_insert(buffer);
3449 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3451 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3452 G_CALLBACK(text_inserted),
3455 cur_encoding = conv_get_locale_charset_str_no_utf8();
3457 while (fgets(buf, sizeof(buf), fp) != NULL) {
3460 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3461 str = g_strdup(buf);
3463 str = conv_codeset_strdup
3464 (buf, cur_encoding, CS_INTERNAL);
3467 /* strip <CR> if DOS/Windows file,
3468 replace <CR> with <LF> if Macintosh file. */
3471 if (len > 0 && str[len - 1] != '\n') {
3473 if (str[len] == '\r') str[len] = '\n';
3476 gtk_text_buffer_insert(buffer, &iter, str, -1);
3480 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3481 G_CALLBACK(text_inserted),
3483 compose->autowrap = prev_autowrap;
3484 if (compose->autowrap)
3485 compose_wrap_all(compose);
3490 return COMPOSE_INSERT_INVALID_CHARACTER;
3492 return COMPOSE_INSERT_SUCCESS;
3495 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3496 const gchar *filename,
3497 const gchar *content_type)
3505 GtkListStore *store;
3507 gboolean has_binary = FALSE;
3509 if (!is_file_exist(file)) {
3510 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3511 gboolean result = FALSE;
3512 if (file_from_uri && is_file_exist(file_from_uri)) {
3513 result = compose_attach_append(
3514 compose, file_from_uri,
3518 g_free(file_from_uri);
3521 alertpanel_error("File %s doesn't exist\n", filename);
3524 if ((size = get_file_size(file)) < 0) {
3525 alertpanel_error("Can't get file size of %s\n", filename);
3529 alertpanel_error(_("File %s is empty."), filename);
3532 if ((fp = g_fopen(file, "rb")) == NULL) {
3533 alertpanel_error(_("Can't read %s."), filename);
3538 ainfo = g_new0(AttachInfo, 1);
3539 auto_ainfo = g_auto_pointer_new_with_free
3540 (ainfo, (GFreeFunc) compose_attach_info_free);
3541 ainfo->file = g_strdup(file);
3544 ainfo->content_type = g_strdup(content_type);
3545 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3547 MsgFlags flags = {0, 0};
3549 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3550 ainfo->encoding = ENC_7BIT;
3552 ainfo->encoding = ENC_8BIT;
3554 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3555 if (msginfo && msginfo->subject)
3556 name = g_strdup(msginfo->subject);
3558 name = g_path_get_basename(filename ? filename : file);
3560 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3562 procmsg_msginfo_free(msginfo);
3564 if (!g_ascii_strncasecmp(content_type, "text", 4))
3565 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3567 ainfo->encoding = ENC_BASE64;
3568 name = g_path_get_basename(filename ? filename : file);
3569 ainfo->name = g_strdup(name);
3573 ainfo->content_type = procmime_get_mime_type(file);
3574 if (!ainfo->content_type) {
3575 ainfo->content_type =
3576 g_strdup("application/octet-stream");
3577 ainfo->encoding = ENC_BASE64;
3578 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3580 procmime_get_encoding_for_text_file(file, &has_binary);
3582 ainfo->encoding = ENC_BASE64;
3583 name = g_path_get_basename(filename ? filename : file);
3584 ainfo->name = g_strdup(name);
3588 if (ainfo->name != NULL
3589 && !strcmp(ainfo->name, ".")) {
3590 g_free(ainfo->name);
3594 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3595 g_free(ainfo->content_type);
3596 ainfo->content_type = g_strdup("application/octet-stream");
3599 ainfo->size = (goffset)size;
3600 size_text = to_human_readable((goffset)size);
3602 store = GTK_LIST_STORE(gtk_tree_view_get_model
3603 (GTK_TREE_VIEW(compose->attach_clist)));
3605 gtk_list_store_append(store, &iter);
3606 gtk_list_store_set(store, &iter,
3607 COL_MIMETYPE, ainfo->content_type,
3608 COL_SIZE, size_text,
3609 COL_NAME, ainfo->name,
3611 COL_AUTODATA, auto_ainfo,
3614 g_auto_pointer_free(auto_ainfo);
3615 compose_attach_update_label(compose);
3619 static void compose_use_signing(Compose *compose, gboolean use_signing)
3621 compose->use_signing = use_signing;
3622 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3625 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3627 compose->use_encryption = use_encryption;
3628 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3631 #define NEXT_PART_NOT_CHILD(info) \
3633 node = info->node; \
3634 while (node->children) \
3635 node = g_node_last_child(node); \
3636 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3639 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3643 MimeInfo *firsttext = NULL;
3644 MimeInfo *encrypted = NULL;
3647 const gchar *partname = NULL;
3649 mimeinfo = procmime_scan_message(msginfo);
3650 if (!mimeinfo) return;
3652 if (mimeinfo->node->children == NULL) {
3653 procmime_mimeinfo_free_all(mimeinfo);
3657 /* find first content part */
3658 child = (MimeInfo *) mimeinfo->node->children->data;
3659 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3660 child = (MimeInfo *)child->node->children->data;
3663 if (child->type == MIMETYPE_TEXT) {
3665 debug_print("First text part found\n");
3666 } else if (compose->mode == COMPOSE_REEDIT &&
3667 child->type == MIMETYPE_APPLICATION &&
3668 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3669 encrypted = (MimeInfo *)child->node->parent->data;
3672 child = (MimeInfo *) mimeinfo->node->children->data;
3673 while (child != NULL) {
3676 if (child == encrypted) {
3677 /* skip this part of tree */
3678 NEXT_PART_NOT_CHILD(child);
3682 if (child->type == MIMETYPE_MULTIPART) {
3683 /* get the actual content */
3684 child = procmime_mimeinfo_next(child);
3688 if (child == firsttext) {
3689 child = procmime_mimeinfo_next(child);
3693 outfile = procmime_get_tmp_file_name(child);
3694 if ((err = procmime_get_part(outfile, child)) < 0)
3695 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3697 gchar *content_type;
3699 content_type = procmime_get_content_type_str(child->type, child->subtype);
3701 /* if we meet a pgp signature, we don't attach it, but
3702 * we force signing. */
3703 if ((strcmp(content_type, "application/pgp-signature") &&
3704 strcmp(content_type, "application/pkcs7-signature") &&
3705 strcmp(content_type, "application/x-pkcs7-signature"))
3706 || compose->mode == COMPOSE_REDIRECT) {
3707 partname = procmime_mimeinfo_get_parameter(child, "filename");
3708 if (partname == NULL)
3709 partname = procmime_mimeinfo_get_parameter(child, "name");
3710 if (partname == NULL)
3712 compose_attach_append(compose, outfile,
3713 partname, content_type);
3715 compose_force_signing(compose, compose->account, NULL);
3717 g_free(content_type);
3720 NEXT_PART_NOT_CHILD(child);
3722 procmime_mimeinfo_free_all(mimeinfo);
3725 #undef NEXT_PART_NOT_CHILD
3730 WAIT_FOR_INDENT_CHAR,
3731 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3734 /* return indent length, we allow:
3735 indent characters followed by indent characters or spaces/tabs,
3736 alphabets and numbers immediately followed by indent characters,
3737 and the repeating sequences of the above
3738 If quote ends with multiple spaces, only the first one is included. */
3739 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3740 const GtkTextIter *start, gint *len)
3742 GtkTextIter iter = *start;
3746 IndentState state = WAIT_FOR_INDENT_CHAR;
3749 gint alnum_count = 0;
3750 gint space_count = 0;
3753 if (prefs_common.quote_chars == NULL) {
3757 while (!gtk_text_iter_ends_line(&iter)) {
3758 wc = gtk_text_iter_get_char(&iter);
3759 if (g_unichar_iswide(wc))
3761 clen = g_unichar_to_utf8(wc, ch);
3765 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3766 is_space = g_unichar_isspace(wc);
3768 if (state == WAIT_FOR_INDENT_CHAR) {
3769 if (!is_indent && !g_unichar_isalnum(wc))
3772 quote_len += alnum_count + space_count + 1;
3773 alnum_count = space_count = 0;
3774 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3777 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3778 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3782 else if (is_indent) {
3783 quote_len += alnum_count + space_count + 1;
3784 alnum_count = space_count = 0;
3787 state = WAIT_FOR_INDENT_CHAR;
3791 gtk_text_iter_forward_char(&iter);
3794 if (quote_len > 0 && space_count > 0)
3800 if (quote_len > 0) {
3802 gtk_text_iter_forward_chars(&iter, quote_len);
3803 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3809 /* return >0 if the line is itemized */
3810 static int compose_itemized_length(GtkTextBuffer *buffer,
3811 const GtkTextIter *start)
3813 GtkTextIter iter = *start;
3818 if (gtk_text_iter_ends_line(&iter))
3823 wc = gtk_text_iter_get_char(&iter);
3824 if (!g_unichar_isspace(wc))
3826 gtk_text_iter_forward_char(&iter);
3827 if (gtk_text_iter_ends_line(&iter))
3831 clen = g_unichar_to_utf8(wc, ch);
3835 if (!strchr("*-+", ch[0]))
3838 gtk_text_iter_forward_char(&iter);
3839 if (gtk_text_iter_ends_line(&iter))
3841 wc = gtk_text_iter_get_char(&iter);
3842 if (g_unichar_isspace(wc)) {
3848 /* return the string at the start of the itemization */
3849 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3850 const GtkTextIter *start)
3852 GtkTextIter iter = *start;
3855 GString *item_chars = g_string_new("");
3858 if (gtk_text_iter_ends_line(&iter))
3863 wc = gtk_text_iter_get_char(&iter);
3864 if (!g_unichar_isspace(wc))
3866 gtk_text_iter_forward_char(&iter);
3867 if (gtk_text_iter_ends_line(&iter))
3869 g_string_append_unichar(item_chars, wc);
3872 str = item_chars->str;
3873 g_string_free(item_chars, FALSE);
3877 /* return the number of spaces at a line's start */
3878 static int compose_left_offset_length(GtkTextBuffer *buffer,
3879 const GtkTextIter *start)
3881 GtkTextIter iter = *start;
3884 if (gtk_text_iter_ends_line(&iter))
3888 wc = gtk_text_iter_get_char(&iter);
3889 if (!g_unichar_isspace(wc))
3892 gtk_text_iter_forward_char(&iter);
3893 if (gtk_text_iter_ends_line(&iter))
3897 gtk_text_iter_forward_char(&iter);
3898 if (gtk_text_iter_ends_line(&iter))
3903 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3904 const GtkTextIter *start,
3905 GtkTextIter *break_pos,
3909 GtkTextIter iter = *start, line_end = *start;
3910 PangoLogAttr *attrs;
3917 gboolean can_break = FALSE;
3918 gboolean do_break = FALSE;
3919 gboolean was_white = FALSE;
3920 gboolean prev_dont_break = FALSE;
3922 gtk_text_iter_forward_to_line_end(&line_end);
3923 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3924 len = g_utf8_strlen(str, -1);
3928 g_warning("compose_get_line_break_pos: len = 0!\n");
3932 /* g_print("breaking line: %d: %s (len = %d)\n",
3933 gtk_text_iter_get_line(&iter), str, len); */
3935 attrs = g_new(PangoLogAttr, len + 1);
3937 pango_default_break(str, -1, NULL, attrs, len + 1);
3941 /* skip quote and leading spaces */
3942 for (i = 0; *p != '\0' && i < len; i++) {
3945 wc = g_utf8_get_char(p);
3946 if (i >= quote_len && !g_unichar_isspace(wc))
3948 if (g_unichar_iswide(wc))
3950 else if (*p == '\t')
3954 p = g_utf8_next_char(p);
3957 for (; *p != '\0' && i < len; i++) {
3958 PangoLogAttr *attr = attrs + i;
3962 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3965 was_white = attr->is_white;
3967 /* don't wrap URI */
3968 if ((uri_len = get_uri_len(p)) > 0) {
3970 if (pos > 0 && col > max_col) {
3980 wc = g_utf8_get_char(p);
3981 if (g_unichar_iswide(wc)) {
3983 if (prev_dont_break && can_break && attr->is_line_break)
3985 } else if (*p == '\t')
3989 if (pos > 0 && col > max_col) {
3994 if (*p == '-' || *p == '/')
3995 prev_dont_break = TRUE;
3997 prev_dont_break = FALSE;
3999 p = g_utf8_next_char(p);
4003 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4008 *break_pos = *start;
4009 gtk_text_iter_set_line_offset(break_pos, pos);
4014 static gboolean compose_join_next_line(Compose *compose,
4015 GtkTextBuffer *buffer,
4017 const gchar *quote_str)
4019 GtkTextIter iter_ = *iter, cur, prev, next, end;
4020 PangoLogAttr attrs[3];
4022 gchar *next_quote_str;
4025 gboolean keep_cursor = FALSE;
4027 if (!gtk_text_iter_forward_line(&iter_) ||
4028 gtk_text_iter_ends_line(&iter_)) {
4031 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4033 if ((quote_str || next_quote_str) &&
4034 strcmp2(quote_str, next_quote_str) != 0) {
4035 g_free(next_quote_str);
4038 g_free(next_quote_str);
4041 if (quote_len > 0) {
4042 gtk_text_iter_forward_chars(&end, quote_len);
4043 if (gtk_text_iter_ends_line(&end)) {
4048 /* don't join itemized lines */
4049 if (compose_itemized_length(buffer, &end) > 0) {
4053 /* don't join signature separator */
4054 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4057 /* delete quote str */
4059 gtk_text_buffer_delete(buffer, &iter_, &end);
4061 /* don't join line breaks put by the user */
4063 gtk_text_iter_backward_char(&cur);
4064 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4065 gtk_text_iter_forward_char(&cur);
4069 gtk_text_iter_forward_char(&cur);
4070 /* delete linebreak and extra spaces */
4071 while (gtk_text_iter_backward_char(&cur)) {
4072 wc1 = gtk_text_iter_get_char(&cur);
4073 if (!g_unichar_isspace(wc1))
4078 while (!gtk_text_iter_ends_line(&cur)) {
4079 wc1 = gtk_text_iter_get_char(&cur);
4080 if (!g_unichar_isspace(wc1))
4082 gtk_text_iter_forward_char(&cur);
4085 if (!gtk_text_iter_equal(&prev, &next)) {
4088 mark = gtk_text_buffer_get_insert(buffer);
4089 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4090 if (gtk_text_iter_equal(&prev, &cur))
4092 gtk_text_buffer_delete(buffer, &prev, &next);
4096 /* insert space if required */
4097 gtk_text_iter_backward_char(&prev);
4098 wc1 = gtk_text_iter_get_char(&prev);
4099 wc2 = gtk_text_iter_get_char(&next);
4100 gtk_text_iter_forward_char(&next);
4101 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4102 pango_default_break(str, -1, NULL, attrs, 3);
4103 if (!attrs[1].is_line_break ||
4104 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4105 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4107 gtk_text_iter_backward_char(&iter_);
4108 gtk_text_buffer_place_cursor(buffer, &iter_);
4117 #define ADD_TXT_POS(bp_, ep_, pti_) \
4118 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4119 last = last->next; \
4120 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4121 last->next = NULL; \
4123 g_warning("alloc error scanning URIs\n"); \
4126 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4128 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4129 GtkTextBuffer *buffer;
4130 GtkTextIter iter, break_pos, end_of_line;
4131 gchar *quote_str = NULL;
4133 gboolean wrap_quote = prefs_common.linewrap_quote;
4134 gboolean prev_autowrap = compose->autowrap;
4135 gint startq_offset = -1, noq_offset = -1;
4136 gint uri_start = -1, uri_stop = -1;
4137 gint nouri_start = -1, nouri_stop = -1;
4138 gint num_blocks = 0;
4139 gint quotelevel = -1;
4140 gboolean modified = force;
4141 gboolean removed = FALSE;
4142 gboolean modified_before_remove = FALSE;
4144 gboolean start = TRUE;
4145 gint itemized_len = 0, rem_item_len = 0;
4146 gchar *itemized_chars = NULL;
4147 gboolean item_continuation = FALSE;
4152 if (compose->draft_timeout_tag == -2) {
4156 compose->autowrap = FALSE;
4158 buffer = gtk_text_view_get_buffer(text);
4159 undo_wrapping(compose->undostruct, TRUE);
4164 mark = gtk_text_buffer_get_insert(buffer);
4165 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4169 if (compose->draft_timeout_tag == -2) {
4170 if (gtk_text_iter_ends_line(&iter)) {
4171 while (gtk_text_iter_ends_line(&iter) &&
4172 gtk_text_iter_forward_line(&iter))
4175 while (gtk_text_iter_backward_line(&iter)) {
4176 if (gtk_text_iter_ends_line(&iter)) {
4177 gtk_text_iter_forward_line(&iter);
4183 /* move to line start */
4184 gtk_text_iter_set_line_offset(&iter, 0);
4187 itemized_len = compose_itemized_length(buffer, &iter);
4189 if (!itemized_len) {
4190 itemized_len = compose_left_offset_length(buffer, &iter);
4191 item_continuation = TRUE;
4195 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4197 /* go until paragraph end (empty line) */
4198 while (start || !gtk_text_iter_ends_line(&iter)) {
4199 gchar *scanpos = NULL;
4200 /* parse table - in order of priority */
4202 const gchar *needle; /* token */
4204 /* token search function */
4205 gchar *(*search) (const gchar *haystack,
4206 const gchar *needle);
4207 /* part parsing function */
4208 gboolean (*parse) (const gchar *start,
4209 const gchar *scanpos,
4213 /* part to URI function */
4214 gchar *(*build_uri) (const gchar *bp,
4218 static struct table parser[] = {
4219 {"http://", strcasestr, get_uri_part, make_uri_string},
4220 {"https://", strcasestr, get_uri_part, make_uri_string},
4221 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4222 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4223 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4224 {"www.", strcasestr, get_uri_part, make_http_string},
4225 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4226 {"@", strcasestr, get_email_part, make_email_string}
4228 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4229 gint last_index = PARSE_ELEMS;
4231 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4235 if (!prev_autowrap && num_blocks == 0) {
4237 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4238 G_CALLBACK(text_inserted),
4241 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4244 uri_start = uri_stop = -1;
4246 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4249 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4250 if (startq_offset == -1)
4251 startq_offset = gtk_text_iter_get_offset(&iter);
4252 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4253 if (quotelevel > 2) {
4254 /* recycle colors */
4255 if (prefs_common.recycle_quote_colors)
4264 if (startq_offset == -1)
4265 noq_offset = gtk_text_iter_get_offset(&iter);
4269 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4272 if (gtk_text_iter_ends_line(&iter)) {
4274 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4275 prefs_common.linewrap_len,
4277 GtkTextIter prev, next, cur;
4278 if (prev_autowrap != FALSE || force) {
4279 compose->automatic_break = TRUE;
4281 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4282 compose->automatic_break = FALSE;
4283 if (itemized_len && compose->autoindent) {
4284 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4285 if (!item_continuation)
4286 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4288 } else if (quote_str && wrap_quote) {
4289 compose->automatic_break = TRUE;
4291 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4292 compose->automatic_break = FALSE;
4293 if (itemized_len && compose->autoindent) {
4294 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4295 if (!item_continuation)
4296 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4300 /* remove trailing spaces */
4302 rem_item_len = itemized_len;
4303 while (compose->autoindent && rem_item_len-- > 0)
4304 gtk_text_iter_backward_char(&cur);
4305 gtk_text_iter_backward_char(&cur);
4308 while (!gtk_text_iter_starts_line(&cur)) {
4311 gtk_text_iter_backward_char(&cur);
4312 wc = gtk_text_iter_get_char(&cur);
4313 if (!g_unichar_isspace(wc))
4317 if (!gtk_text_iter_equal(&prev, &next)) {
4318 gtk_text_buffer_delete(buffer, &prev, &next);
4320 gtk_text_iter_forward_char(&break_pos);
4324 gtk_text_buffer_insert(buffer, &break_pos,
4328 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4330 /* move iter to current line start */
4331 gtk_text_iter_set_line_offset(&iter, 0);
4338 /* move iter to next line start */
4344 if (!prev_autowrap && num_blocks > 0) {
4346 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4347 G_CALLBACK(text_inserted),
4351 while (!gtk_text_iter_ends_line(&end_of_line)) {
4352 gtk_text_iter_forward_char(&end_of_line);
4354 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4356 nouri_start = gtk_text_iter_get_offset(&iter);
4357 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4359 walk_pos = gtk_text_iter_get_offset(&iter);
4360 /* FIXME: this looks phony. scanning for anything in the parse table */
4361 for (n = 0; n < PARSE_ELEMS; n++) {
4364 tmp = parser[n].search(walk, parser[n].needle);
4366 if (scanpos == NULL || tmp < scanpos) {
4375 /* check if URI can be parsed */
4376 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4377 (const gchar **)&ep, FALSE)
4378 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4382 strlen(parser[last_index].needle);
4385 uri_start = walk_pos + (bp - o_walk);
4386 uri_stop = walk_pos + (ep - o_walk);
4390 gtk_text_iter_forward_line(&iter);
4393 if (startq_offset != -1) {
4394 GtkTextIter startquote, endquote;
4395 gtk_text_buffer_get_iter_at_offset(
4396 buffer, &startquote, startq_offset);
4399 switch (quotelevel) {
4401 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4402 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4403 gtk_text_buffer_apply_tag_by_name(
4404 buffer, "quote0", &startquote, &endquote);
4405 gtk_text_buffer_remove_tag_by_name(
4406 buffer, "quote1", &startquote, &endquote);
4407 gtk_text_buffer_remove_tag_by_name(
4408 buffer, "quote2", &startquote, &endquote);
4413 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4414 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4415 gtk_text_buffer_apply_tag_by_name(
4416 buffer, "quote1", &startquote, &endquote);
4417 gtk_text_buffer_remove_tag_by_name(
4418 buffer, "quote0", &startquote, &endquote);
4419 gtk_text_buffer_remove_tag_by_name(
4420 buffer, "quote2", &startquote, &endquote);
4425 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4426 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4427 gtk_text_buffer_apply_tag_by_name(
4428 buffer, "quote2", &startquote, &endquote);
4429 gtk_text_buffer_remove_tag_by_name(
4430 buffer, "quote0", &startquote, &endquote);
4431 gtk_text_buffer_remove_tag_by_name(
4432 buffer, "quote1", &startquote, &endquote);
4438 } else if (noq_offset != -1) {
4439 GtkTextIter startnoquote, endnoquote;
4440 gtk_text_buffer_get_iter_at_offset(
4441 buffer, &startnoquote, noq_offset);
4444 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4445 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4446 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4447 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4448 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4449 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4450 gtk_text_buffer_remove_tag_by_name(
4451 buffer, "quote0", &startnoquote, &endnoquote);
4452 gtk_text_buffer_remove_tag_by_name(
4453 buffer, "quote1", &startnoquote, &endnoquote);
4454 gtk_text_buffer_remove_tag_by_name(
4455 buffer, "quote2", &startnoquote, &endnoquote);
4461 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4462 GtkTextIter nouri_start_iter, nouri_end_iter;
4463 gtk_text_buffer_get_iter_at_offset(
4464 buffer, &nouri_start_iter, nouri_start);
4465 gtk_text_buffer_get_iter_at_offset(
4466 buffer, &nouri_end_iter, nouri_stop);
4467 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4468 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4469 gtk_text_buffer_remove_tag_by_name(
4470 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4471 modified_before_remove = modified;
4476 if (uri_start >= 0 && uri_stop > 0) {
4477 GtkTextIter uri_start_iter, uri_end_iter, back;
4478 gtk_text_buffer_get_iter_at_offset(
4479 buffer, &uri_start_iter, uri_start);
4480 gtk_text_buffer_get_iter_at_offset(
4481 buffer, &uri_end_iter, uri_stop);
4482 back = uri_end_iter;
4483 gtk_text_iter_backward_char(&back);
4484 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4485 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4486 gtk_text_buffer_apply_tag_by_name(
4487 buffer, "link", &uri_start_iter, &uri_end_iter);
4489 if (removed && !modified_before_remove) {
4495 // debug_print("not modified, out after %d lines\n", lines);
4499 // debug_print("modified, out after %d lines\n", lines);
4501 g_free(itemized_chars);
4504 undo_wrapping(compose->undostruct, FALSE);
4505 compose->autowrap = prev_autowrap;
4510 void compose_action_cb(void *data)
4512 Compose *compose = (Compose *)data;
4513 compose_wrap_all(compose);
4516 static void compose_wrap_all(Compose *compose)
4518 compose_wrap_all_full(compose, FALSE);
4521 static void compose_wrap_all_full(Compose *compose, gboolean force)
4523 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4524 GtkTextBuffer *buffer;
4526 gboolean modified = TRUE;
4528 buffer = gtk_text_view_get_buffer(text);
4530 gtk_text_buffer_get_start_iter(buffer, &iter);
4531 while (!gtk_text_iter_is_end(&iter) && modified)
4532 modified = compose_beautify_paragraph(compose, &iter, force);
4536 static void compose_set_title(Compose *compose)
4542 edited = compose->modified ? _(" [Edited]") : "";
4544 subject = gtk_editable_get_chars(
4545 GTK_EDITABLE(compose->subject_entry), 0, -1);
4547 #ifndef GENERIC_UMPC
4548 if (subject && strlen(subject))
4549 str = g_strdup_printf(_("%s - Compose message%s"),
4552 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4554 str = g_strdup(_("Compose message"));
4557 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4563 * compose_current_mail_account:
4565 * Find a current mail account (the currently selected account, or the
4566 * default account, if a news account is currently selected). If a
4567 * mail account cannot be found, display an error message.
4569 * Return value: Mail account, or NULL if not found.
4571 static PrefsAccount *
4572 compose_current_mail_account(void)
4576 if (cur_account && cur_account->protocol != A_NNTP)
4579 ac = account_get_default();
4580 if (!ac || ac->protocol == A_NNTP) {
4581 alertpanel_error(_("Account for sending mail is not specified.\n"
4582 "Please select a mail account before sending."));
4589 #define QUOTE_IF_REQUIRED(out, str) \
4591 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4595 len = strlen(str) + 3; \
4596 if ((__tmp = alloca(len)) == NULL) { \
4597 g_warning("can't allocate memory\n"); \
4598 g_string_free(header, TRUE); \
4601 g_snprintf(__tmp, len, "\"%s\"", str); \
4606 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4607 g_warning("can't allocate memory\n"); \
4608 g_string_free(header, TRUE); \
4611 strcpy(__tmp, str); \
4617 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4619 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4623 len = strlen(str) + 3; \
4624 if ((__tmp = alloca(len)) == NULL) { \
4625 g_warning("can't allocate memory\n"); \
4628 g_snprintf(__tmp, len, "\"%s\"", str); \
4633 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4634 g_warning("can't allocate memory\n"); \
4637 strcpy(__tmp, str); \
4643 static void compose_select_account(Compose *compose, PrefsAccount *account,
4646 gchar *from = NULL, *header;
4647 ComposeHeaderEntry *header_entry;
4649 cm_return_if_fail(account != NULL);
4651 compose->account = account;
4652 if (account->name && *account->name) {
4654 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4655 from = g_strdup_printf("%s <%s>",
4656 buf, account->address);
4657 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4659 from = g_strdup_printf("<%s>",
4661 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4666 compose_set_title(compose);
4668 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4669 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4671 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4672 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4673 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4675 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4677 activate_privacy_system(compose, account, FALSE);
4679 if (!init && compose->mode != COMPOSE_REDIRECT) {
4680 undo_block(compose->undostruct);
4681 compose_insert_sig(compose, TRUE);
4682 undo_unblock(compose->undostruct);
4685 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4686 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4688 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4689 if (account->protocol == A_NNTP) {
4690 if (!strcmp(header, _("To:")))
4691 combobox_select_by_text(
4692 GTK_COMBO_BOX(header_entry->combo),
4695 if (!strcmp(header, _("Newsgroups:")))
4696 combobox_select_by_text(
4697 GTK_COMBO_BOX(header_entry->combo),
4705 /* use account's dict info if set */
4706 if (compose->gtkaspell) {
4707 if (account->enable_default_dictionary)
4708 gtkaspell_change_dict(compose->gtkaspell,
4709 account->default_dictionary, FALSE);
4710 if (account->enable_default_alt_dictionary)
4711 gtkaspell_change_alt_dict(compose->gtkaspell,
4712 account->default_alt_dictionary);
4713 if (account->enable_default_dictionary
4714 || account->enable_default_alt_dictionary)
4715 compose_spell_menu_changed(compose);
4720 gboolean compose_check_for_valid_recipient(Compose *compose) {
4721 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4722 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4723 gboolean recipient_found = FALSE;
4727 /* free to and newsgroup list */
4728 slist_free_strings(compose->to_list);
4729 g_slist_free(compose->to_list);
4730 compose->to_list = NULL;
4732 slist_free_strings(compose->newsgroup_list);
4733 g_slist_free(compose->newsgroup_list);
4734 compose->newsgroup_list = NULL;
4736 /* search header entries for to and newsgroup entries */
4737 for (list = compose->header_list; list; list = list->next) {
4740 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4741 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4744 if (entry[0] != '\0') {
4745 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4746 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4747 compose->to_list = address_list_append(compose->to_list, entry);
4748 recipient_found = TRUE;
4751 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4752 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4753 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4754 recipient_found = TRUE;
4761 return recipient_found;
4764 static gboolean compose_check_for_set_recipients(Compose *compose)
4766 if (compose->account->set_autocc && compose->account->auto_cc) {
4767 gboolean found_other = FALSE;
4769 /* search header entries for to and newsgroup entries */
4770 for (list = compose->header_list; list; list = list->next) {
4773 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4774 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4777 if (strcmp(entry, compose->account->auto_cc)
4778 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4788 if (compose->batch) {
4789 gtk_widget_show_all(compose->window);
4791 aval = alertpanel(_("Send"),
4792 _("The only recipient is the default CC address. Send anyway?"),
4793 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4794 if (aval != G_ALERTALTERNATE)
4798 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4799 gboolean found_other = FALSE;
4801 /* search header entries for to and newsgroup entries */
4802 for (list = compose->header_list; list; list = list->next) {
4805 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4806 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4809 if (strcmp(entry, compose->account->auto_bcc)
4810 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4820 if (compose->batch) {
4821 gtk_widget_show_all(compose->window);
4823 aval = alertpanel(_("Send"),
4824 _("The only recipient is the default BCC address. Send anyway?"),
4825 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4826 if (aval != G_ALERTALTERNATE)
4833 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4837 if (compose_check_for_valid_recipient(compose) == FALSE) {
4838 if (compose->batch) {
4839 gtk_widget_show_all(compose->window);
4841 alertpanel_error(_("Recipient is not specified."));
4845 if (compose_check_for_set_recipients(compose) == FALSE) {
4849 if (!compose->batch) {
4850 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4851 if (*str == '\0' && check_everything == TRUE &&
4852 compose->mode != COMPOSE_REDIRECT) {
4854 gchar *button_label;
4857 if (compose->sending)
4858 button_label = _("+_Send");
4860 button_label = _("+_Queue");
4861 message = g_strdup_printf(_("Subject is empty. %s"),
4862 compose->sending?_("Send it anyway?"):
4863 _("Queue it anyway?"));
4865 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4866 GTK_STOCK_CANCEL, button_label, NULL);
4868 if (aval != G_ALERTALTERNATE)
4873 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4879 gint compose_send(Compose *compose)
4882 FolderItem *folder = NULL;
4884 gchar *msgpath = NULL;
4885 gboolean discard_window = FALSE;
4886 gchar *errstr = NULL;
4887 gchar *tmsgid = NULL;
4888 MainWindow *mainwin = mainwindow_get_mainwindow();
4889 gboolean queued_removed = FALSE;
4891 if (prefs_common.send_dialog_invisible
4892 || compose->batch == TRUE)
4893 discard_window = TRUE;
4895 compose_allow_user_actions (compose, FALSE);
4896 compose->sending = TRUE;
4898 if (compose_check_entries(compose, TRUE) == FALSE) {
4899 if (compose->batch) {
4900 gtk_widget_show_all(compose->window);
4906 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4909 if (compose->batch) {
4910 gtk_widget_show_all(compose->window);
4913 alertpanel_error(_("Could not queue message for sending:\n\n"
4914 "Charset conversion failed."));
4915 } else if (val == -5) {
4916 alertpanel_error(_("Could not queue message for sending:\n\n"
4917 "Couldn't get recipient encryption key."));
4918 } else if (val == -6) {
4920 } else if (val == -3) {
4921 if (privacy_peek_error())
4922 alertpanel_error(_("Could not queue message for sending:\n\n"
4923 "Signature failed: %s"), privacy_get_error());
4924 } else if (val == -2 && errno != 0) {
4925 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4927 alertpanel_error(_("Could not queue message for sending."));
4932 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4933 if (discard_window) {
4934 compose->sending = FALSE;
4935 compose_close(compose);
4936 /* No more compose access in the normal codepath
4937 * after this point! */
4942 alertpanel_error(_("The message was queued but could not be "
4943 "sent.\nUse \"Send queued messages\" from "
4944 "the main window to retry."));
4945 if (!discard_window) {
4952 if (msgpath == NULL) {
4953 msgpath = folder_item_fetch_msg(folder, msgnum);
4954 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4957 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4958 claws_unlink(msgpath);
4961 if (!discard_window) {
4963 if (!queued_removed)
4964 folder_item_remove_msg(folder, msgnum);
4965 folder_item_scan(folder);
4967 /* make sure we delete that */
4968 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4970 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4971 folder_item_remove_msg(folder, tmp->msgnum);
4972 procmsg_msginfo_free(tmp);
4979 if (!queued_removed)
4980 folder_item_remove_msg(folder, msgnum);
4981 folder_item_scan(folder);
4983 /* make sure we delete that */
4984 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4986 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4987 folder_item_remove_msg(folder, tmp->msgnum);
4988 procmsg_msginfo_free(tmp);
4991 if (!discard_window) {
4992 compose->sending = FALSE;
4993 compose_allow_user_actions (compose, TRUE);
4994 compose_close(compose);
4998 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4999 "the main window to retry."), errstr);
5002 alertpanel_error_log(_("The message was queued but could not be "
5003 "sent.\nUse \"Send queued messages\" from "
5004 "the main window to retry."));
5006 if (!discard_window) {
5015 toolbar_main_set_sensitive(mainwin);
5016 main_window_set_menu_sensitive(mainwin);
5022 compose_allow_user_actions (compose, TRUE);
5023 compose->sending = FALSE;
5024 compose->modified = TRUE;
5025 toolbar_main_set_sensitive(mainwin);
5026 main_window_set_menu_sensitive(mainwin);
5031 static gboolean compose_use_attach(Compose *compose)
5033 GtkTreeModel *model = gtk_tree_view_get_model
5034 (GTK_TREE_VIEW(compose->attach_clist));
5035 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5038 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5041 gchar buf[BUFFSIZE];
5043 gboolean first_to_address;
5044 gboolean first_cc_address;
5046 ComposeHeaderEntry *headerentry;
5047 const gchar *headerentryname;
5048 const gchar *cc_hdr;
5049 const gchar *to_hdr;
5050 gboolean err = FALSE;
5052 debug_print("Writing redirect header\n");
5054 cc_hdr = prefs_common_translated_header_name("Cc:");
5055 to_hdr = prefs_common_translated_header_name("To:");
5057 first_to_address = TRUE;
5058 for (list = compose->header_list; list; list = list->next) {
5059 headerentry = ((ComposeHeaderEntry *)list->data);
5060 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5062 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5063 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5064 Xstrdup_a(str, entstr, return -1);
5066 if (str[0] != '\0') {
5067 compose_convert_header
5068 (compose, buf, sizeof(buf), str,
5069 strlen("Resent-To") + 2, TRUE);
5071 if (first_to_address) {
5072 err |= (fprintf(fp, "Resent-To: ") < 0);
5073 first_to_address = FALSE;
5075 err |= (fprintf(fp, ",") < 0);
5077 err |= (fprintf(fp, "%s", buf) < 0);
5081 if (!first_to_address) {
5082 err |= (fprintf(fp, "\n") < 0);
5085 first_cc_address = TRUE;
5086 for (list = compose->header_list; list; list = list->next) {
5087 headerentry = ((ComposeHeaderEntry *)list->data);
5088 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5090 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5091 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5092 Xstrdup_a(str, strg, return -1);
5094 if (str[0] != '\0') {
5095 compose_convert_header
5096 (compose, buf, sizeof(buf), str,
5097 strlen("Resent-Cc") + 2, TRUE);
5099 if (first_cc_address) {
5100 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5101 first_cc_address = FALSE;
5103 err |= (fprintf(fp, ",") < 0);
5105 err |= (fprintf(fp, "%s", buf) < 0);
5109 if (!first_cc_address) {
5110 err |= (fprintf(fp, "\n") < 0);
5113 return (err ? -1:0);
5116 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5118 gchar buf[BUFFSIZE];
5120 const gchar *entstr;
5121 /* struct utsname utsbuf; */
5122 gboolean err = FALSE;
5124 cm_return_val_if_fail(fp != NULL, -1);
5125 cm_return_val_if_fail(compose->account != NULL, -1);
5126 cm_return_val_if_fail(compose->account->address != NULL, -1);
5129 get_rfc822_date(buf, sizeof(buf));
5130 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5133 if (compose->account->name && *compose->account->name) {
5134 compose_convert_header
5135 (compose, buf, sizeof(buf), compose->account->name,
5136 strlen("From: "), TRUE);
5137 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5138 buf, compose->account->address) < 0);
5140 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5143 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5144 if (*entstr != '\0') {
5145 Xstrdup_a(str, entstr, return -1);
5148 compose_convert_header(compose, buf, sizeof(buf), str,
5149 strlen("Subject: "), FALSE);
5150 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5154 /* Resent-Message-ID */
5155 if (compose->account->set_domain && compose->account->domain) {
5156 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5157 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5158 g_snprintf(buf, sizeof(buf), "%s",
5159 strchr(compose->account->address, '@') ?
5160 strchr(compose->account->address, '@')+1 :
5161 compose->account->address);
5163 g_snprintf(buf, sizeof(buf), "%s", "");
5166 if (compose->account->gen_msgid) {
5168 if (compose->account->msgid_with_addr) {
5169 addr = compose->account->address;
5171 generate_msgid(buf, sizeof(buf), addr);
5172 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5173 compose->msgid = g_strdup(buf);
5175 compose->msgid = NULL;
5178 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5181 /* separator between header and body */
5182 err |= (fputs("\n", fp) == EOF);
5184 return (err ? -1:0);
5187 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5191 gchar buf[BUFFSIZE];
5193 gboolean skip = FALSE;
5194 gboolean err = FALSE;
5195 gchar *not_included[]={
5196 "Return-Path:", "Delivered-To:", "Received:",
5197 "Subject:", "X-UIDL:", "AF:",
5198 "NF:", "PS:", "SRH:",
5199 "SFN:", "DSR:", "MID:",
5200 "CFG:", "PT:", "S:",
5201 "RQ:", "SSV:", "NSV:",
5202 "SSH:", "R:", "MAID:",
5203 "NAID:", "RMID:", "FMID:",
5204 "SCF:", "RRCPT:", "NG:",
5205 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5206 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5207 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5208 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5209 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5212 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5213 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5217 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5219 for (i = 0; not_included[i] != NULL; i++) {
5220 if (g_ascii_strncasecmp(buf, not_included[i],
5221 strlen(not_included[i])) == 0) {
5228 if (fputs(buf, fdest) == -1)
5231 if (!prefs_common.redirect_keep_from) {
5232 if (g_ascii_strncasecmp(buf, "From:",
5233 strlen("From:")) == 0) {
5234 err |= (fputs(" (by way of ", fdest) == EOF);
5235 if (compose->account->name
5236 && *compose->account->name) {
5237 compose_convert_header
5238 (compose, buf, sizeof(buf),
5239 compose->account->name,
5242 err |= (fprintf(fdest, "%s <%s>",
5244 compose->account->address) < 0);
5246 err |= (fprintf(fdest, "%s",
5247 compose->account->address) < 0);
5248 err |= (fputs(")", fdest) == EOF);
5252 if (fputs("\n", fdest) == -1)
5259 if (compose_redirect_write_headers(compose, fdest))
5262 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5263 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5276 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5278 GtkTextBuffer *buffer;
5279 GtkTextIter start, end;
5282 const gchar *out_codeset;
5283 EncodingType encoding = ENC_UNKNOWN;
5284 MimeInfo *mimemsg, *mimetext;
5286 const gchar *src_codeset = CS_INTERNAL;
5287 gchar *from_addr = NULL;
5288 gchar *from_name = NULL;
5290 if (action == COMPOSE_WRITE_FOR_SEND)
5291 attach_parts = TRUE;
5293 /* create message MimeInfo */
5294 mimemsg = procmime_mimeinfo_new();
5295 mimemsg->type = MIMETYPE_MESSAGE;
5296 mimemsg->subtype = g_strdup("rfc822");
5297 mimemsg->content = MIMECONTENT_MEM;
5298 mimemsg->tmp = TRUE; /* must free content later */
5299 mimemsg->data.mem = compose_get_header(compose);
5301 /* Create text part MimeInfo */
5302 /* get all composed text */
5303 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5304 gtk_text_buffer_get_start_iter(buffer, &start);
5305 gtk_text_buffer_get_end_iter(buffer, &end);
5306 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5308 out_codeset = conv_get_charset_str(compose->out_encoding);
5310 if (!out_codeset && is_ascii_str(chars)) {
5311 out_codeset = CS_US_ASCII;
5312 } else if (prefs_common.outgoing_fallback_to_ascii &&
5313 is_ascii_str(chars)) {
5314 out_codeset = CS_US_ASCII;
5315 encoding = ENC_7BIT;
5319 gchar *test_conv_global_out = NULL;
5320 gchar *test_conv_reply = NULL;
5322 /* automatic mode. be automatic. */
5323 codeconv_set_strict(TRUE);
5325 out_codeset = conv_get_outgoing_charset_str();
5327 debug_print("trying to convert to %s\n", out_codeset);
5328 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5331 if (!test_conv_global_out && compose->orig_charset
5332 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5333 out_codeset = compose->orig_charset;
5334 debug_print("failure; trying to convert to %s\n", out_codeset);
5335 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5338 if (!test_conv_global_out && !test_conv_reply) {
5340 out_codeset = CS_INTERNAL;
5341 debug_print("failure; finally using %s\n", out_codeset);
5343 g_free(test_conv_global_out);
5344 g_free(test_conv_reply);
5345 codeconv_set_strict(FALSE);
5348 if (encoding == ENC_UNKNOWN) {
5349 if (prefs_common.encoding_method == CTE_BASE64)
5350 encoding = ENC_BASE64;
5351 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5352 encoding = ENC_QUOTED_PRINTABLE;
5353 else if (prefs_common.encoding_method == CTE_8BIT)
5354 encoding = ENC_8BIT;
5356 encoding = procmime_get_encoding_for_charset(out_codeset);
5359 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5360 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5362 if (action == COMPOSE_WRITE_FOR_SEND) {
5363 codeconv_set_strict(TRUE);
5364 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5365 codeconv_set_strict(FALSE);
5371 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5372 "to the specified %s charset.\n"
5373 "Send it as %s?"), out_codeset, src_codeset);
5374 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5375 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5378 if (aval != G_ALERTALTERNATE) {
5383 out_codeset = src_codeset;
5389 out_codeset = src_codeset;
5394 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5395 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5396 strstr(buf, "\nFrom ") != NULL) {
5397 encoding = ENC_QUOTED_PRINTABLE;
5401 mimetext = procmime_mimeinfo_new();
5402 mimetext->content = MIMECONTENT_MEM;
5403 mimetext->tmp = TRUE; /* must free content later */
5404 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5405 * and free the data, which we need later. */
5406 mimetext->data.mem = g_strdup(buf);
5407 mimetext->type = MIMETYPE_TEXT;
5408 mimetext->subtype = g_strdup("plain");
5409 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5410 g_strdup(out_codeset));
5412 /* protect trailing spaces when signing message */
5413 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5414 privacy_system_can_sign(compose->privacy_system)) {
5415 encoding = ENC_QUOTED_PRINTABLE;
5418 debug_print("main text: %zd bytes encoded as %s in %d\n",
5419 strlen(buf), out_codeset, encoding);
5421 /* check for line length limit */
5422 if (action == COMPOSE_WRITE_FOR_SEND &&
5423 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5424 check_line_length(buf, 1000, &line) < 0) {
5428 msg = g_strdup_printf
5429 (_("Line %d exceeds the line length limit (998 bytes).\n"
5430 "The contents of the message might be broken on the way to the delivery.\n"
5432 "Send it anyway?"), line + 1);
5433 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5435 if (aval != G_ALERTALTERNATE) {
5441 if (encoding != ENC_UNKNOWN)
5442 procmime_encode_content(mimetext, encoding);
5444 /* append attachment parts */
5445 if (compose_use_attach(compose) && attach_parts) {
5446 MimeInfo *mimempart;
5447 gchar *boundary = NULL;
5448 mimempart = procmime_mimeinfo_new();
5449 mimempart->content = MIMECONTENT_EMPTY;
5450 mimempart->type = MIMETYPE_MULTIPART;
5451 mimempart->subtype = g_strdup("mixed");
5455 boundary = generate_mime_boundary(NULL);
5456 } while (strstr(buf, boundary) != NULL);
5458 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5461 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5463 g_node_append(mimempart->node, mimetext->node);
5464 g_node_append(mimemsg->node, mimempart->node);
5466 if (compose_add_attachments(compose, mimempart) < 0)
5469 g_node_append(mimemsg->node, mimetext->node);
5473 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5474 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5475 /* extract name and address */
5476 if (strstr(spec, " <") && strstr(spec, ">")) {
5477 from_addr = g_strdup(strrchr(spec, '<')+1);
5478 *(strrchr(from_addr, '>')) = '\0';
5479 from_name = g_strdup(spec);
5480 *(strrchr(from_name, '<')) = '\0';
5487 /* sign message if sending */
5488 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5489 privacy_system_can_sign(compose->privacy_system))
5490 if (!privacy_sign(compose->privacy_system, mimemsg,
5491 compose->account, from_addr)) {
5498 procmime_write_mimeinfo(mimemsg, fp);
5500 procmime_mimeinfo_free_all(mimemsg);
5505 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5507 GtkTextBuffer *buffer;
5508 GtkTextIter start, end;
5513 if ((fp = g_fopen(file, "wb")) == NULL) {
5514 FILE_OP_ERROR(file, "fopen");
5518 /* chmod for security */
5519 if (change_file_mode_rw(fp, file) < 0) {
5520 FILE_OP_ERROR(file, "chmod");
5521 g_warning("can't change file mode\n");
5524 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5525 gtk_text_buffer_get_start_iter(buffer, &start);
5526 gtk_text_buffer_get_end_iter(buffer, &end);
5527 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5529 chars = conv_codeset_strdup
5530 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5533 if (!chars) return -1;
5536 len = strlen(chars);
5537 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5538 FILE_OP_ERROR(file, "fwrite");
5547 if (fclose(fp) == EOF) {
5548 FILE_OP_ERROR(file, "fclose");
5555 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5558 MsgInfo *msginfo = compose->targetinfo;
5560 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5561 if (!msginfo) return -1;
5563 if (!force && MSG_IS_LOCKED(msginfo->flags))
5566 item = msginfo->folder;
5567 cm_return_val_if_fail(item != NULL, -1);
5569 if (procmsg_msg_exist(msginfo) &&
5570 (folder_has_parent_of_type(item, F_QUEUE) ||
5571 folder_has_parent_of_type(item, F_DRAFT)
5572 || msginfo == compose->autosaved_draft)) {
5573 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5574 g_warning("can't remove the old message\n");
5577 debug_print("removed reedit target %d\n", msginfo->msgnum);
5584 static void compose_remove_draft(Compose *compose)
5587 MsgInfo *msginfo = compose->targetinfo;
5588 drafts = account_get_special_folder(compose->account, F_DRAFT);
5590 if (procmsg_msg_exist(msginfo)) {
5591 folder_item_remove_msg(drafts, msginfo->msgnum);
5596 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5597 gboolean remove_reedit_target)
5599 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5602 static gboolean compose_warn_encryption(Compose *compose)
5604 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5605 AlertValue val = G_ALERTALTERNATE;
5607 if (warning == NULL)
5610 val = alertpanel_full(_("Encryption warning"), warning,
5611 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5612 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5613 if (val & G_ALERTDISABLE) {
5614 val &= ~G_ALERTDISABLE;
5615 if (val == G_ALERTALTERNATE)
5616 privacy_inhibit_encrypt_warning(compose->privacy_system,
5620 if (val == G_ALERTALTERNATE) {
5627 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5628 gchar **msgpath, gboolean check_subject,
5629 gboolean remove_reedit_target)
5636 static gboolean lock = FALSE;
5637 PrefsAccount *mailac = NULL, *newsac = NULL;
5638 gboolean err = FALSE;
5640 debug_print("queueing message...\n");
5641 cm_return_val_if_fail(compose->account != NULL, -1);
5645 if (compose_check_entries(compose, check_subject) == FALSE) {
5647 if (compose->batch) {
5648 gtk_widget_show_all(compose->window);
5653 if (!compose->to_list && !compose->newsgroup_list) {
5654 g_warning("can't get recipient list.");
5659 if (compose->to_list) {
5660 if (compose->account->protocol != A_NNTP)
5661 mailac = compose->account;
5662 else if (cur_account && cur_account->protocol != A_NNTP)
5663 mailac = cur_account;
5664 else if (!(mailac = compose_current_mail_account())) {
5666 alertpanel_error(_("No account for sending mails available!"));
5671 if (compose->newsgroup_list) {
5672 if (compose->account->protocol == A_NNTP)
5673 newsac = compose->account;
5676 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5681 /* write queue header */
5682 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5683 G_DIR_SEPARATOR, compose, (guint) rand());
5684 debug_print("queuing to %s\n", tmp);
5685 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5686 FILE_OP_ERROR(tmp, "fopen");
5692 if (change_file_mode_rw(fp, tmp) < 0) {
5693 FILE_OP_ERROR(tmp, "chmod");
5694 g_warning("can't change file mode\n");
5697 /* queueing variables */
5698 err |= (fprintf(fp, "AF:\n") < 0);
5699 err |= (fprintf(fp, "NF:0\n") < 0);
5700 err |= (fprintf(fp, "PS:10\n") < 0);
5701 err |= (fprintf(fp, "SRH:1\n") < 0);
5702 err |= (fprintf(fp, "SFN:\n") < 0);
5703 err |= (fprintf(fp, "DSR:\n") < 0);
5705 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5707 err |= (fprintf(fp, "MID:\n") < 0);
5708 err |= (fprintf(fp, "CFG:\n") < 0);
5709 err |= (fprintf(fp, "PT:0\n") < 0);
5710 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5711 err |= (fprintf(fp, "RQ:\n") < 0);
5713 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5715 err |= (fprintf(fp, "SSV:\n") < 0);
5717 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5719 err |= (fprintf(fp, "NSV:\n") < 0);
5720 err |= (fprintf(fp, "SSH:\n") < 0);
5721 /* write recepient list */
5722 if (compose->to_list) {
5723 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5724 for (cur = compose->to_list->next; cur != NULL;
5726 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5727 err |= (fprintf(fp, "\n") < 0);
5729 /* write newsgroup list */
5730 if (compose->newsgroup_list) {
5731 err |= (fprintf(fp, "NG:") < 0);
5732 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5733 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5734 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5735 err |= (fprintf(fp, "\n") < 0);
5737 /* Sylpheed account IDs */
5739 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5741 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5744 if (compose->privacy_system != NULL) {
5745 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5746 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5747 if (compose->use_encryption) {
5749 if (!compose_warn_encryption(compose)) {
5756 if (mailac && mailac->encrypt_to_self) {
5757 GSList *tmp_list = g_slist_copy(compose->to_list);
5758 tmp_list = g_slist_append(tmp_list, compose->account->address);
5759 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5760 g_slist_free(tmp_list);
5762 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5764 if (encdata != NULL) {
5765 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5766 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5767 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5769 } /* else we finally dont want to encrypt */
5771 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5772 /* and if encdata was null, it means there's been a problem in
5784 /* Save copy folder */
5785 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5786 gchar *savefolderid;
5788 savefolderid = compose_get_save_to(compose);
5789 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5790 g_free(savefolderid);
5792 /* Save copy folder */
5793 if (compose->return_receipt) {
5794 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5796 /* Message-ID of message replying to */
5797 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5800 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5801 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5804 /* Message-ID of message forwarding to */
5805 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5808 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5809 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5813 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5814 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5816 /* end of headers */
5817 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5819 if (compose->redirect_filename != NULL) {
5820 if (compose_redirect_write_to_file(compose, fp) < 0) {
5829 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5834 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5838 g_warning("failed to write queue message\n");
5845 if (fclose(fp) == EOF) {
5846 FILE_OP_ERROR(tmp, "fclose");
5853 if (item && *item) {
5856 queue = account_get_special_folder(compose->account, F_QUEUE);
5859 g_warning("can't find queue folder\n");
5865 folder_item_scan(queue);
5866 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5867 g_warning("can't queue the message\n");
5874 if (msgpath == NULL) {
5880 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5881 compose_remove_reedit_target(compose, FALSE);
5884 if ((msgnum != NULL) && (item != NULL)) {
5892 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5895 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5897 struct stat statbuf;
5898 gchar *type, *subtype;
5899 GtkTreeModel *model;
5902 model = gtk_tree_view_get_model(tree_view);
5904 if (!gtk_tree_model_get_iter_first(model, &iter))
5907 gtk_tree_model_get(model, &iter,
5911 if (!is_file_exist(ainfo->file)) {
5912 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5913 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5914 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5916 if (val == G_ALERTDEFAULT) {
5921 mimepart = procmime_mimeinfo_new();
5922 mimepart->content = MIMECONTENT_FILE;
5923 mimepart->data.filename = g_strdup(ainfo->file);
5924 mimepart->tmp = FALSE; /* or we destroy our attachment */
5925 mimepart->offset = 0;
5927 g_stat(ainfo->file, &statbuf);
5928 mimepart->length = statbuf.st_size;
5930 type = g_strdup(ainfo->content_type);
5932 if (!strchr(type, '/')) {
5934 type = g_strdup("application/octet-stream");
5937 subtype = strchr(type, '/') + 1;
5938 *(subtype - 1) = '\0';
5939 mimepart->type = procmime_get_media_type(type);
5940 mimepart->subtype = g_strdup(subtype);
5943 if (mimepart->type == MIMETYPE_MESSAGE &&
5944 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5945 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5948 if (mimepart->type == MIMETYPE_APPLICATION &&
5949 !strcmp2(mimepart->subtype, "octet-stream"))
5950 g_hash_table_insert(mimepart->typeparameters,
5951 g_strdup("name"), g_strdup(ainfo->name));
5952 g_hash_table_insert(mimepart->dispositionparameters,
5953 g_strdup("filename"), g_strdup(ainfo->name));
5954 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5958 if (compose->use_signing) {
5959 if (ainfo->encoding == ENC_7BIT)
5960 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5961 else if (ainfo->encoding == ENC_8BIT)
5962 ainfo->encoding = ENC_BASE64;
5965 procmime_encode_content(mimepart, ainfo->encoding);
5967 g_node_append(parent->node, mimepart->node);
5968 } while (gtk_tree_model_iter_next(model, &iter));
5973 #define IS_IN_CUSTOM_HEADER(header) \
5974 (compose->account->add_customhdr && \
5975 custom_header_find(compose->account->customhdr_list, header) != NULL)
5977 static void compose_add_headerfield_from_headerlist(Compose *compose,
5979 const gchar *fieldname,
5980 const gchar *seperator)
5982 gchar *str, *fieldname_w_colon;
5983 gboolean add_field = FALSE;
5985 ComposeHeaderEntry *headerentry;
5986 const gchar *headerentryname;
5987 const gchar *trans_fieldname;
5990 if (IS_IN_CUSTOM_HEADER(fieldname))
5993 debug_print("Adding %s-fields\n", fieldname);
5995 fieldstr = g_string_sized_new(64);
5997 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5998 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6000 for (list = compose->header_list; list; list = list->next) {
6001 headerentry = ((ComposeHeaderEntry *)list->data);
6002 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6004 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6005 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6007 if (str[0] != '\0') {
6009 g_string_append(fieldstr, seperator);
6010 g_string_append(fieldstr, str);
6019 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6020 compose_convert_header
6021 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6022 strlen(fieldname) + 2, TRUE);
6023 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6027 g_free(fieldname_w_colon);
6028 g_string_free(fieldstr, TRUE);
6033 static gchar *compose_get_header(Compose *compose)
6035 gchar buf[BUFFSIZE];
6036 const gchar *entry_str;
6040 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6042 gchar *from_name = NULL, *from_address = NULL;
6045 cm_return_val_if_fail(compose->account != NULL, NULL);
6046 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6048 header = g_string_sized_new(64);
6051 get_rfc822_date(buf, sizeof(buf));
6052 g_string_append_printf(header, "Date: %s\n", buf);
6056 if (compose->account->name && *compose->account->name) {
6058 QUOTE_IF_REQUIRED(buf, compose->account->name);
6059 tmp = g_strdup_printf("%s <%s>",
6060 buf, compose->account->address);
6062 tmp = g_strdup_printf("%s",
6063 compose->account->address);
6065 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6066 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6068 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6069 from_address = g_strdup(compose->account->address);
6071 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6072 /* extract name and address */
6073 if (strstr(spec, " <") && strstr(spec, ">")) {
6074 from_address = g_strdup(strrchr(spec, '<')+1);
6075 *(strrchr(from_address, '>')) = '\0';
6076 from_name = g_strdup(spec);
6077 *(strrchr(from_name, '<')) = '\0';
6080 from_address = g_strdup(spec);
6087 if (from_name && *from_name) {
6088 compose_convert_header
6089 (compose, buf, sizeof(buf), from_name,
6090 strlen("From: "), TRUE);
6091 QUOTE_IF_REQUIRED(name, buf);
6093 g_string_append_printf(header, "From: %s <%s>\n",
6094 name, from_address);
6096 g_string_append_printf(header, "From: %s\n", from_address);
6099 g_free(from_address);
6102 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6105 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6108 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6111 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6114 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6116 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6119 compose_convert_header(compose, buf, sizeof(buf), str,
6120 strlen("Subject: "), FALSE);
6121 g_string_append_printf(header, "Subject: %s\n", buf);
6127 if (compose->account->set_domain && compose->account->domain) {
6128 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6129 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6130 g_snprintf(buf, sizeof(buf), "%s",
6131 strchr(compose->account->address, '@') ?
6132 strchr(compose->account->address, '@')+1 :
6133 compose->account->address);
6135 g_snprintf(buf, sizeof(buf), "%s", "");
6138 if (compose->account->gen_msgid) {
6140 if (compose->account->msgid_with_addr) {
6141 addr = compose->account->address;
6143 generate_msgid(buf, sizeof(buf), addr);
6144 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6145 compose->msgid = g_strdup(buf);
6147 compose->msgid = NULL;
6150 if (compose->remove_references == FALSE) {
6152 if (compose->inreplyto && compose->to_list)
6153 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6156 if (compose->references)
6157 g_string_append_printf(header, "References: %s\n", compose->references);
6161 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6164 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6167 if (compose->account->organization &&
6168 strlen(compose->account->organization) &&
6169 !IS_IN_CUSTOM_HEADER("Organization")) {
6170 compose_convert_header(compose, buf, sizeof(buf),
6171 compose->account->organization,
6172 strlen("Organization: "), FALSE);
6173 g_string_append_printf(header, "Organization: %s\n", buf);
6176 /* Program version and system info */
6177 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6178 !compose->newsgroup_list) {
6179 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6181 gtk_major_version, gtk_minor_version, gtk_micro_version,
6184 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6185 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6187 gtk_major_version, gtk_minor_version, gtk_micro_version,
6191 /* custom headers */
6192 if (compose->account->add_customhdr) {
6195 for (cur = compose->account->customhdr_list; cur != NULL;
6197 CustomHeader *chdr = (CustomHeader *)cur->data;
6199 if (custom_header_is_allowed(chdr->name)) {
6200 compose_convert_header
6201 (compose, buf, sizeof(buf),
6202 chdr->value ? chdr->value : "",
6203 strlen(chdr->name) + 2, FALSE);
6204 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6209 /* Automatic Faces and X-Faces */
6210 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6211 g_string_append_printf(header, "X-Face: %s\n", buf);
6213 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6214 g_string_append_printf(header, "X-Face: %s\n", buf);
6216 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6217 g_string_append_printf(header, "Face: %s\n", buf);
6219 else if (get_default_face (buf, sizeof(buf)) == 0) {
6220 g_string_append_printf(header, "Face: %s\n", buf);
6224 switch (compose->priority) {
6225 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6226 "X-Priority: 1 (Highest)\n");
6228 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6229 "X-Priority: 2 (High)\n");
6231 case PRIORITY_NORMAL: break;
6232 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6233 "X-Priority: 4 (Low)\n");
6235 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6236 "X-Priority: 5 (Lowest)\n");
6238 default: debug_print("compose: priority unknown : %d\n",
6242 /* Request Return Receipt */
6243 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6244 if (compose->return_receipt) {
6245 if (compose->account->name
6246 && *compose->account->name) {
6247 compose_convert_header(compose, buf, sizeof(buf),
6248 compose->account->name,
6249 strlen("Disposition-Notification-To: "),
6251 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6253 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6257 /* get special headers */
6258 for (list = compose->header_list; list; list = list->next) {
6259 ComposeHeaderEntry *headerentry;
6262 gchar *headername_wcolon;
6263 const gchar *headername_trans;
6266 gboolean standard_header = FALSE;
6268 headerentry = ((ComposeHeaderEntry *)list->data);
6270 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6272 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6277 if (!strstr(tmp, ":")) {
6278 headername_wcolon = g_strconcat(tmp, ":", NULL);
6279 headername = g_strdup(tmp);
6281 headername_wcolon = g_strdup(tmp);
6282 headername = g_strdup(strtok(tmp, ":"));
6286 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6287 Xstrdup_a(headervalue, entry_str, return NULL);
6288 subst_char(headervalue, '\r', ' ');
6289 subst_char(headervalue, '\n', ' ');
6290 string = std_headers;
6291 while (*string != NULL) {
6292 headername_trans = prefs_common_translated_header_name(*string);
6293 if (!strcmp(headername_trans, headername_wcolon))
6294 standard_header = TRUE;
6297 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6298 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6301 g_free(headername_wcolon);
6305 g_string_free(header, FALSE);
6310 #undef IS_IN_CUSTOM_HEADER
6312 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6313 gint header_len, gboolean addr_field)
6315 gchar *tmpstr = NULL;
6316 const gchar *out_codeset = NULL;
6318 cm_return_if_fail(src != NULL);
6319 cm_return_if_fail(dest != NULL);
6321 if (len < 1) return;
6323 tmpstr = g_strdup(src);
6325 subst_char(tmpstr, '\n', ' ');
6326 subst_char(tmpstr, '\r', ' ');
6329 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6330 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6331 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6336 codeconv_set_strict(TRUE);
6337 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6338 conv_get_charset_str(compose->out_encoding));
6339 codeconv_set_strict(FALSE);
6341 if (!dest || *dest == '\0') {
6342 gchar *test_conv_global_out = NULL;
6343 gchar *test_conv_reply = NULL;
6345 /* automatic mode. be automatic. */
6346 codeconv_set_strict(TRUE);
6348 out_codeset = conv_get_outgoing_charset_str();
6350 debug_print("trying to convert to %s\n", out_codeset);
6351 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6354 if (!test_conv_global_out && compose->orig_charset
6355 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6356 out_codeset = compose->orig_charset;
6357 debug_print("failure; trying to convert to %s\n", out_codeset);
6358 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6361 if (!test_conv_global_out && !test_conv_reply) {
6363 out_codeset = CS_INTERNAL;
6364 debug_print("finally using %s\n", out_codeset);
6366 g_free(test_conv_global_out);
6367 g_free(test_conv_reply);
6368 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6370 codeconv_set_strict(FALSE);
6375 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6379 cm_return_if_fail(user_data != NULL);
6381 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6382 g_strstrip(address);
6383 if (*address != '\0') {
6384 gchar *name = procheader_get_fromname(address);
6385 extract_address(address);
6386 addressbook_add_contact(name, address, NULL, NULL);
6391 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6393 GtkWidget *menuitem;
6396 cm_return_if_fail(menu != NULL);
6397 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6399 menuitem = gtk_separator_menu_item_new();
6400 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6401 gtk_widget_show(menuitem);
6403 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6404 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6406 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6407 g_strstrip(address);
6408 if (*address == '\0') {
6409 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6412 g_signal_connect(G_OBJECT(menuitem), "activate",
6413 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6414 gtk_widget_show(menuitem);
6417 static void compose_create_header_entry(Compose *compose)
6419 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6426 const gchar *header = NULL;
6427 ComposeHeaderEntry *headerentry;
6428 gboolean standard_header = FALSE;
6429 GtkListStore *model;
6431 #if !(GTK_CHECK_VERSION(2,12,0))
6432 GtkTooltips *tips = compose->tooltips;
6435 headerentry = g_new0(ComposeHeaderEntry, 1);
6438 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6439 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6440 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6442 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6444 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6446 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6447 COMPOSE_NEWSGROUPS);
6448 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6450 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6451 COMPOSE_FOLLOWUPTO);
6453 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6454 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6455 G_CALLBACK(compose_grab_focus_cb), compose);
6456 gtk_widget_show(combo);
6457 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6458 compose->header_nextrow, compose->header_nextrow+1,
6459 GTK_SHRINK, GTK_FILL, 0, 0);
6460 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6461 const gchar *last_header_entry = gtk_entry_get_text(
6462 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6464 while (*string != NULL) {
6465 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6466 standard_header = TRUE;
6469 if (standard_header)
6470 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6472 if (!compose->header_last || !standard_header) {
6473 switch(compose->account->protocol) {
6475 header = prefs_common_translated_header_name("Newsgroups:");
6478 header = prefs_common_translated_header_name("To:");
6483 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6485 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6486 G_CALLBACK(compose_grab_focus_cb), compose);
6488 /* Entry field with cleanup button */
6489 #if GTK_CHECK_VERSION(2, 8, 0)
6490 button = gtk_button_new();
6491 gtk_button_set_image(GTK_BUTTON(button),
6492 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6494 button = gtk_button_new_with_label(_("Clear"));
6496 gtk_widget_show(button);
6497 CLAWS_SET_TIP(button,
6498 _("Delete entry contents"));
6499 entry = gtk_entry_new();
6500 gtk_widget_show(entry);
6501 CLAWS_SET_TIP(entry,
6502 _("Use <tab> to autocomplete from addressbook"));
6503 hbox = gtk_hbox_new (FALSE, 0);
6504 gtk_widget_show(hbox);
6505 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6506 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6507 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6508 compose->header_nextrow, compose->header_nextrow+1,
6509 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6511 g_signal_connect(G_OBJECT(entry), "key-press-event",
6512 G_CALLBACK(compose_headerentry_key_press_event_cb),
6514 g_signal_connect(G_OBJECT(entry), "changed",
6515 G_CALLBACK(compose_headerentry_changed_cb),
6517 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6518 G_CALLBACK(compose_grab_focus_cb), compose);
6520 g_signal_connect(G_OBJECT(button), "clicked",
6521 G_CALLBACK(compose_headerentry_button_clicked_cb),
6525 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6526 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6527 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6528 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6529 G_CALLBACK(compose_header_drag_received_cb),
6531 g_signal_connect(G_OBJECT(entry), "drag-drop",
6532 G_CALLBACK(compose_drag_drop),
6534 g_signal_connect(G_OBJECT(entry), "populate-popup",
6535 G_CALLBACK(compose_entry_popup_extend),
6538 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6540 headerentry->compose = compose;
6541 headerentry->combo = combo;
6542 headerentry->entry = entry;
6543 headerentry->button = button;
6544 headerentry->hbox = hbox;
6545 headerentry->headernum = compose->header_nextrow;
6546 headerentry->type = PREF_NONE;
6548 compose->header_nextrow++;
6549 compose->header_last = headerentry;
6550 compose->header_list =
6551 g_slist_append(compose->header_list,
6555 static void compose_add_header_entry(Compose *compose, const gchar *header,
6556 gchar *text, ComposePrefType pref_type)
6558 ComposeHeaderEntry *last_header = compose->header_last;
6559 gchar *tmp = g_strdup(text), *email;
6560 gboolean replyto_hdr = g_str_has_suffix(header, "-To:");
6562 extract_address(tmp);
6563 email = g_utf8_strdown(tmp, -1);
6565 if (replyto_hdr == FALSE &&
6566 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6568 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6569 header, text, (gint) pref_type);
6575 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6576 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6577 last_header->type = pref_type;
6579 if (replyto_hdr == FALSE)
6580 g_hash_table_insert(compose->email_hashtable, email,
6581 GUINT_TO_POINTER(1));
6588 static void compose_destroy_headerentry(Compose *compose,
6589 ComposeHeaderEntry *headerentry)
6591 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6594 extract_address(text);
6595 email = g_utf8_strdown(text, -1);
6596 g_hash_table_remove(compose->email_hashtable, email);
6600 gtk_widget_destroy(headerentry->combo);
6601 gtk_widget_destroy(headerentry->entry);
6602 gtk_widget_destroy(headerentry->button);
6603 gtk_widget_destroy(headerentry->hbox);
6604 g_free(headerentry);
6607 static void compose_remove_header_entries(Compose *compose)
6610 for (list = compose->header_list; list; list = list->next)
6611 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6613 compose->header_last = NULL;
6614 g_slist_free(compose->header_list);
6615 compose->header_list = NULL;
6616 compose->header_nextrow = 1;
6617 compose_create_header_entry(compose);
6620 static GtkWidget *compose_create_header(Compose *compose)
6622 GtkWidget *from_optmenu_hbox;
6623 GtkWidget *header_scrolledwin;
6624 GtkWidget *header_table;
6628 /* header labels and entries */
6629 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6630 gtk_widget_show(header_scrolledwin);
6631 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6633 header_table = gtk_table_new(2, 2, FALSE);
6634 gtk_widget_show(header_table);
6635 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6636 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6637 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6640 /* option menu for selecting accounts */
6641 from_optmenu_hbox = compose_account_option_menu_create(compose);
6642 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6643 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6646 compose->header_table = header_table;
6647 compose->header_list = NULL;
6648 compose->header_nextrow = count;
6650 compose_create_header_entry(compose);
6652 compose->table = NULL;
6654 return header_scrolledwin ;
6657 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6659 Compose *compose = (Compose *)data;
6660 GdkEventButton event;
6663 event.time = gtk_get_current_event_time();
6665 return attach_button_pressed(compose->attach_clist, &event, compose);
6668 static GtkWidget *compose_create_attach(Compose *compose)
6670 GtkWidget *attach_scrwin;
6671 GtkWidget *attach_clist;
6673 GtkListStore *store;
6674 GtkCellRenderer *renderer;
6675 GtkTreeViewColumn *column;
6676 GtkTreeSelection *selection;
6678 /* attachment list */
6679 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6680 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6681 GTK_POLICY_AUTOMATIC,
6682 GTK_POLICY_AUTOMATIC);
6683 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6685 store = gtk_list_store_new(N_ATTACH_COLS,
6690 G_TYPE_AUTO_POINTER,
6692 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6693 (GTK_TREE_MODEL(store)));
6694 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6695 g_object_unref(store);
6697 renderer = gtk_cell_renderer_text_new();
6698 column = gtk_tree_view_column_new_with_attributes
6699 (_("Mime type"), renderer, "text",
6700 COL_MIMETYPE, NULL);
6701 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6703 renderer = gtk_cell_renderer_text_new();
6704 column = gtk_tree_view_column_new_with_attributes
6705 (_("Size"), renderer, "text",
6707 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6709 renderer = gtk_cell_renderer_text_new();
6710 column = gtk_tree_view_column_new_with_attributes
6711 (_("Name"), renderer, "text",
6713 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6715 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6716 prefs_common.use_stripes_everywhere);
6717 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6718 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6720 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6721 G_CALLBACK(attach_selected), compose);
6722 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6723 G_CALLBACK(attach_button_pressed), compose);
6725 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6726 G_CALLBACK(popup_attach_button_pressed), compose);
6728 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6729 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6730 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6731 G_CALLBACK(popup_attach_button_pressed), compose);
6733 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6734 G_CALLBACK(attach_key_pressed), compose);
6737 gtk_drag_dest_set(attach_clist,
6738 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6739 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6740 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6741 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6742 G_CALLBACK(compose_attach_drag_received_cb),
6744 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6745 G_CALLBACK(compose_drag_drop),
6748 compose->attach_scrwin = attach_scrwin;
6749 compose->attach_clist = attach_clist;
6751 return attach_scrwin;
6754 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6755 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6757 static GtkWidget *compose_create_others(Compose *compose)
6760 GtkWidget *savemsg_checkbtn;
6761 GtkWidget *savemsg_combo;
6762 GtkWidget *savemsg_select;
6765 gchar *folderidentifier;
6767 /* Table for settings */
6768 table = gtk_table_new(3, 1, FALSE);
6769 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6770 gtk_widget_show(table);
6771 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6774 /* Save Message to folder */
6775 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6776 gtk_widget_show(savemsg_checkbtn);
6777 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6778 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6779 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6781 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6782 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6784 savemsg_combo = gtk_combo_box_entry_new_text();
6785 compose->savemsg_checkbtn = savemsg_checkbtn;
6786 compose->savemsg_combo = savemsg_combo;
6787 gtk_widget_show(savemsg_combo);
6789 if (prefs_common.compose_save_to_history)
6790 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6791 prefs_common.compose_save_to_history);
6793 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6794 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6795 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6796 G_CALLBACK(compose_grab_focus_cb), compose);
6797 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6798 folderidentifier = folder_item_get_identifier(account_get_special_folder
6799 (compose->account, F_OUTBOX));
6800 compose_set_save_to(compose, folderidentifier);
6801 g_free(folderidentifier);
6804 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6805 gtk_widget_show(savemsg_select);
6806 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6807 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6808 G_CALLBACK(compose_savemsg_select_cb),
6816 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6818 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6819 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6822 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6827 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6830 path = folder_item_get_identifier(dest);
6832 compose_set_save_to(compose, path);
6836 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6837 GdkAtom clip, GtkTextIter *insert_place);
6840 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6844 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6846 if (event->button == 3) {
6848 GtkTextIter sel_start, sel_end;
6849 gboolean stuff_selected;
6851 /* move the cursor to allow GtkAspell to check the word
6852 * under the mouse */
6853 if (event->x && event->y) {
6854 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6855 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6857 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6860 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6861 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6864 stuff_selected = gtk_text_buffer_get_selection_bounds(
6866 &sel_start, &sel_end);
6868 gtk_text_buffer_place_cursor (buffer, &iter);
6869 /* reselect stuff */
6871 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6872 gtk_text_buffer_select_range(buffer,
6873 &sel_start, &sel_end);
6875 return FALSE; /* pass the event so that the right-click goes through */
6878 if (event->button == 2) {
6883 /* get the middle-click position to paste at the correct place */
6884 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6885 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6887 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6890 entry_paste_clipboard(compose, text,
6891 prefs_common.linewrap_pastes,
6892 GDK_SELECTION_PRIMARY, &iter);
6900 static void compose_spell_menu_changed(void *data)
6902 Compose *compose = (Compose *)data;
6904 GtkWidget *menuitem;
6905 GtkWidget *parent_item;
6906 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6909 if (compose->gtkaspell == NULL)
6912 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6913 "/Menu/Spelling/Options");
6915 /* setting the submenu removes /Spelling/Options from the factory
6916 * so we need to save it */
6918 if (parent_item == NULL) {
6919 parent_item = compose->aspell_options_menu;
6920 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6922 compose->aspell_options_menu = parent_item;
6924 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6926 spell_menu = g_slist_reverse(spell_menu);
6927 for (items = spell_menu;
6928 items; items = items->next) {
6929 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6930 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6931 gtk_widget_show(GTK_WIDGET(menuitem));
6933 g_slist_free(spell_menu);
6935 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6936 gtk_widget_show(parent_item);
6939 static void compose_dict_changed(void *data)
6941 Compose *compose = (Compose *) data;
6943 if(compose->gtkaspell &&
6944 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6947 gtkaspell_highlight_all(compose->gtkaspell);
6948 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6952 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6954 Compose *compose = (Compose *)data;
6955 GdkEventButton event;
6958 event.time = gtk_get_current_event_time();
6962 return text_clicked(compose->text, &event, compose);
6965 static gboolean compose_force_window_origin = TRUE;
6966 static Compose *compose_create(PrefsAccount *account,
6975 GtkWidget *handlebox;
6977 GtkWidget *notebook;
6979 GtkWidget *attach_hbox;
6980 GtkWidget *attach_lab1;
6981 GtkWidget *attach_lab2;
6986 GtkWidget *subject_hbox;
6987 GtkWidget *subject_frame;
6988 GtkWidget *subject_entry;
6992 GtkWidget *edit_vbox;
6993 GtkWidget *ruler_hbox;
6995 GtkWidget *scrolledwin;
6997 GtkTextBuffer *buffer;
6998 GtkClipboard *clipboard;
7001 UndoMain *undostruct;
7003 gchar *titles[N_ATTACH_COLS];
7004 GtkWidget *popupmenu;
7005 GtkWidget *tmpl_menu;
7006 GtkActionGroup *action_group = NULL;
7009 GtkAspell * gtkaspell = NULL;
7012 static GdkGeometry geometry;
7014 cm_return_val_if_fail(account != NULL, NULL);
7016 debug_print("Creating compose window...\n");
7017 compose = g_new0(Compose, 1);
7019 titles[COL_MIMETYPE] = _("MIME type");
7020 titles[COL_SIZE] = _("Size");
7021 titles[COL_NAME] = _("Name");
7023 compose->batch = batch;
7024 compose->account = account;
7025 compose->folder = folder;
7027 compose->mutex = g_mutex_new();
7028 compose->set_cursor_pos = -1;
7030 #if !(GTK_CHECK_VERSION(2,12,0))
7031 compose->tooltips = tips;
7034 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7036 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7037 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7039 if (!geometry.max_width) {
7040 geometry.max_width = gdk_screen_width();
7041 geometry.max_height = gdk_screen_height();
7044 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7045 &geometry, GDK_HINT_MAX_SIZE);
7046 if (!geometry.min_width) {
7047 geometry.min_width = 600;
7048 geometry.min_height = 440;
7050 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7051 &geometry, GDK_HINT_MIN_SIZE);
7053 #ifndef GENERIC_UMPC
7054 if (compose_force_window_origin)
7055 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7056 prefs_common.compose_y);
7058 g_signal_connect(G_OBJECT(window), "delete_event",
7059 G_CALLBACK(compose_delete_cb), compose);
7060 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7061 gtk_widget_realize(window);
7063 gtkut_widget_set_composer_icon(window);
7065 vbox = gtk_vbox_new(FALSE, 0);
7066 gtk_container_add(GTK_CONTAINER(window), vbox);
7068 compose->ui_manager = gtk_ui_manager_new();
7069 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7070 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7071 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7072 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7073 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7074 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7075 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7076 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7077 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7078 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7081 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7083 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7086 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7087 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7089 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7091 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7092 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7093 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7096 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7097 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7098 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7099 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7100 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7103 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7105 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7110 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7112 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7116 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7123 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7131 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7133 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7145 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7175 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7181 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7183 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7191 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7198 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)
7199 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)
7200 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7205 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)
7206 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)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7211 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)
7212 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7215 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)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7221 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)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7226 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7227 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)
7228 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)
7229 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7230 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7232 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7233 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7235 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7239 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7240 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)
7242 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7243 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7244 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7248 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7249 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7250 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7251 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7252 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7253 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7256 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7258 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7259 gtk_widget_show_all(menubar);
7261 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7263 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7265 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7268 if (prefs_common.toolbar_detachable) {
7269 handlebox = gtk_handle_box_new();
7271 handlebox = gtk_hbox_new(FALSE, 0);
7273 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7275 gtk_widget_realize(handlebox);
7277 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7280 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7284 vbox2 = gtk_vbox_new(FALSE, 2);
7285 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7286 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7289 notebook = gtk_notebook_new();
7290 gtk_widget_set_size_request(notebook, -1, 130);
7291 gtk_widget_show(notebook);
7293 /* header labels and entries */
7294 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7295 compose_create_header(compose),
7296 gtk_label_new_with_mnemonic(_("Hea_der")));
7297 /* attachment list */
7298 attach_hbox = gtk_hbox_new(FALSE, 0);
7299 gtk_widget_show(attach_hbox);
7301 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7302 gtk_widget_show(attach_lab1);
7303 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7305 attach_lab2 = gtk_label_new("");
7306 gtk_widget_show(attach_lab2);
7307 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7309 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7310 compose_create_attach(compose),
7313 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7314 compose_create_others(compose),
7315 gtk_label_new_with_mnemonic(_("Othe_rs")));
7318 subject_hbox = gtk_hbox_new(FALSE, 0);
7319 gtk_widget_show(subject_hbox);
7321 subject_frame = gtk_frame_new(NULL);
7322 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7323 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7324 gtk_widget_show(subject_frame);
7326 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7327 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7328 gtk_widget_show(subject);
7330 label = gtk_label_new(_("Subject:"));
7331 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7332 gtk_widget_show(label);
7335 subject_entry = claws_spell_entry_new();
7337 subject_entry = gtk_entry_new();
7339 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7340 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7341 G_CALLBACK(compose_grab_focus_cb), compose);
7342 gtk_widget_show(subject_entry);
7343 compose->subject_entry = subject_entry;
7344 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7346 edit_vbox = gtk_vbox_new(FALSE, 0);
7348 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7351 ruler_hbox = gtk_hbox_new(FALSE, 0);
7352 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7354 ruler = gtk_shruler_new();
7355 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7356 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7360 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7361 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7362 GTK_POLICY_AUTOMATIC,
7363 GTK_POLICY_AUTOMATIC);
7364 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7366 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7367 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7369 text = gtk_text_view_new();
7370 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7371 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7372 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7373 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7374 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7376 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7378 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7379 G_CALLBACK(compose_edit_size_alloc),
7381 g_signal_connect(G_OBJECT(buffer), "changed",
7382 G_CALLBACK(compose_changed_cb), compose);
7383 g_signal_connect(G_OBJECT(text), "grab_focus",
7384 G_CALLBACK(compose_grab_focus_cb), compose);
7385 g_signal_connect(G_OBJECT(buffer), "insert_text",
7386 G_CALLBACK(text_inserted), compose);
7387 g_signal_connect(G_OBJECT(text), "button_press_event",
7388 G_CALLBACK(text_clicked), compose);
7390 g_signal_connect(G_OBJECT(text), "popup-menu",
7391 G_CALLBACK(compose_popup_menu), compose);
7393 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7394 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7395 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7396 G_CALLBACK(compose_popup_menu), compose);
7398 g_signal_connect(G_OBJECT(subject_entry), "changed",
7399 G_CALLBACK(compose_changed_cb), compose);
7402 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7403 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7404 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7405 g_signal_connect(G_OBJECT(text), "drag_data_received",
7406 G_CALLBACK(compose_insert_drag_received_cb),
7408 g_signal_connect(G_OBJECT(text), "drag-drop",
7409 G_CALLBACK(compose_drag_drop),
7411 gtk_widget_show_all(vbox);
7413 /* pane between attach clist and text */
7414 paned = gtk_vpaned_new();
7415 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7417 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7418 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7420 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7422 gtk_paned_add1(GTK_PANED(paned), notebook);
7423 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7424 gtk_widget_show_all(paned);
7427 if (prefs_common.textfont) {
7428 PangoFontDescription *font_desc;
7430 font_desc = pango_font_description_from_string
7431 (prefs_common.textfont);
7433 gtk_widget_modify_font(text, font_desc);
7434 pango_font_description_free(font_desc);
7438 gtk_action_group_add_actions(action_group, compose_popup_entries,
7439 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7444 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7445 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7447 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7449 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7450 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7451 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7453 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7455 undostruct = undo_init(text);
7456 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7459 address_completion_start(window);
7461 compose->window = window;
7462 compose->vbox = vbox;
7463 compose->menubar = menubar;
7464 compose->handlebox = handlebox;
7466 compose->vbox2 = vbox2;
7468 compose->paned = paned;
7470 compose->attach_label = attach_lab2;
7472 compose->notebook = notebook;
7473 compose->edit_vbox = edit_vbox;
7474 compose->ruler_hbox = ruler_hbox;
7475 compose->ruler = ruler;
7476 compose->scrolledwin = scrolledwin;
7477 compose->text = text;
7479 compose->focused_editable = NULL;
7481 compose->popupmenu = popupmenu;
7483 compose->tmpl_menu = tmpl_menu;
7485 compose->mode = mode;
7486 compose->rmode = mode;
7488 compose->targetinfo = NULL;
7489 compose->replyinfo = NULL;
7490 compose->fwdinfo = NULL;
7492 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7493 g_str_equal, (GDestroyNotify) g_free, NULL);
7495 compose->replyto = NULL;
7497 compose->bcc = NULL;
7498 compose->followup_to = NULL;
7500 compose->ml_post = NULL;
7502 compose->inreplyto = NULL;
7503 compose->references = NULL;
7504 compose->msgid = NULL;
7505 compose->boundary = NULL;
7507 compose->autowrap = prefs_common.autowrap;
7508 compose->autoindent = prefs_common.auto_indent;
7509 compose->use_signing = FALSE;
7510 compose->use_encryption = FALSE;
7511 compose->privacy_system = NULL;
7513 compose->modified = FALSE;
7515 compose->return_receipt = FALSE;
7517 compose->to_list = NULL;
7518 compose->newsgroup_list = NULL;
7520 compose->undostruct = undostruct;
7522 compose->sig_str = NULL;
7524 compose->exteditor_file = NULL;
7525 compose->exteditor_pid = -1;
7526 compose->exteditor_tag = -1;
7527 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7530 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7531 if (mode != COMPOSE_REDIRECT) {
7532 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7533 strcmp(prefs_common.dictionary, "")) {
7534 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7535 prefs_common.alt_dictionary,
7536 conv_get_locale_charset_str(),
7537 prefs_common.misspelled_col,
7538 prefs_common.check_while_typing,
7539 prefs_common.recheck_when_changing_dict,
7540 prefs_common.use_alternate,
7541 prefs_common.use_both_dicts,
7542 GTK_TEXT_VIEW(text),
7543 GTK_WINDOW(compose->window),
7544 compose_dict_changed,
7545 compose_spell_menu_changed,
7548 alertpanel_error(_("Spell checker could not "
7550 gtkaspell_checkers_strerror());
7551 gtkaspell_checkers_reset_error();
7553 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7557 compose->gtkaspell = gtkaspell;
7558 compose_spell_menu_changed(compose);
7559 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7562 compose_select_account(compose, account, TRUE);
7564 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7565 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7567 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7568 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7570 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7571 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7573 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7574 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7576 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7577 if (account->protocol != A_NNTP)
7578 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7579 prefs_common_translated_header_name("To:"));
7581 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7582 prefs_common_translated_header_name("Newsgroups:"));
7584 addressbook_set_target_compose(compose);
7586 if (mode != COMPOSE_REDIRECT)
7587 compose_set_template_menu(compose);
7589 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7592 compose_list = g_list_append(compose_list, compose);
7594 if (!prefs_common.show_ruler)
7595 gtk_widget_hide(ruler_hbox);
7597 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7600 compose->priority = PRIORITY_NORMAL;
7601 compose_update_priority_menu_item(compose);
7603 compose_set_out_encoding(compose);
7606 compose_update_actions_menu(compose);
7608 /* Privacy Systems menu */
7609 compose_update_privacy_systems_menu(compose);
7611 activate_privacy_system(compose, account, TRUE);
7612 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7614 gtk_widget_realize(window);
7616 gtk_widget_show(window);
7618 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7619 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7626 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7631 GtkWidget *optmenubox;
7634 GtkWidget *from_name = NULL;
7635 #if !(GTK_CHECK_VERSION(2,12,0))
7636 GtkTooltips *tips = compose->tooltips;
7639 gint num = 0, def_menu = 0;
7641 accounts = account_get_list();
7642 cm_return_val_if_fail(accounts != NULL, NULL);
7644 optmenubox = gtk_event_box_new();
7645 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7646 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7648 hbox = gtk_hbox_new(FALSE, 6);
7649 from_name = gtk_entry_new();
7651 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7652 G_CALLBACK(compose_grab_focus_cb), compose);
7654 for (; accounts != NULL; accounts = accounts->next, num++) {
7655 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7656 gchar *name, *from = NULL;
7658 if (ac == compose->account) def_menu = num;
7660 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7663 if (ac == compose->account) {
7664 if (ac->name && *ac->name) {
7666 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7667 from = g_strdup_printf("%s <%s>",
7669 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7671 from = g_strdup_printf("%s",
7673 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7676 COMBOBOX_ADD(menu, name, ac->account_id);
7681 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7683 g_signal_connect(G_OBJECT(optmenu), "changed",
7684 G_CALLBACK(account_activated),
7686 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7687 G_CALLBACK(compose_entry_popup_extend),
7690 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7691 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7693 CLAWS_SET_TIP(optmenubox,
7694 _("Account to use for this email"));
7695 CLAWS_SET_TIP(from_name,
7696 _("Sender address to be used"));
7698 compose->from_name = from_name;
7703 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7705 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7706 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7707 Compose *compose = (Compose *) data;
7709 compose->priority = value;
7713 static void compose_reply_change_mode(Compose *compose,
7716 gboolean was_modified = compose->modified;
7718 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7720 cm_return_if_fail(compose->replyinfo != NULL);
7722 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7724 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7726 if (action == COMPOSE_REPLY_TO_ALL)
7728 if (action == COMPOSE_REPLY_TO_SENDER)
7730 if (action == COMPOSE_REPLY_TO_LIST)
7733 compose_remove_header_entries(compose);
7734 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7735 if (compose->account->set_autocc && compose->account->auto_cc)
7736 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7738 if (compose->account->set_autobcc && compose->account->auto_bcc)
7739 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7741 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7742 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7743 compose_show_first_last_header(compose, TRUE);
7744 compose->modified = was_modified;
7745 compose_set_title(compose);
7748 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7750 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7751 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7752 Compose *compose = (Compose *) data;
7755 compose_reply_change_mode(compose, value);
7758 static void compose_update_priority_menu_item(Compose * compose)
7760 GtkWidget *menuitem = NULL;
7761 switch (compose->priority) {
7762 case PRIORITY_HIGHEST:
7763 menuitem = gtk_ui_manager_get_widget
7764 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7767 menuitem = gtk_ui_manager_get_widget
7768 (compose->ui_manager, "/Menu/Options/Priority/High");
7770 case PRIORITY_NORMAL:
7771 menuitem = gtk_ui_manager_get_widget
7772 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7775 menuitem = gtk_ui_manager_get_widget
7776 (compose->ui_manager, "/Menu/Options/Priority/Low");
7778 case PRIORITY_LOWEST:
7779 menuitem = gtk_ui_manager_get_widget
7780 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7783 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7786 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7788 Compose *compose = (Compose *) data;
7790 gboolean can_sign = FALSE, can_encrypt = FALSE;
7792 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7794 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7797 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7798 g_free(compose->privacy_system);
7799 compose->privacy_system = NULL;
7800 if (systemid != NULL) {
7801 compose->privacy_system = g_strdup(systemid);
7803 can_sign = privacy_system_can_sign(systemid);
7804 can_encrypt = privacy_system_can_encrypt(systemid);
7807 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7809 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7810 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7813 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7815 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7816 GtkWidget *menuitem = NULL;
7818 gboolean can_sign = FALSE, can_encrypt = FALSE;
7819 gboolean found = FALSE;
7821 if (compose->privacy_system != NULL) {
7823 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7824 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7825 cm_return_if_fail(menuitem != NULL);
7827 amenu = GTK_MENU_SHELL(menuitem)->children;
7829 while (amenu != NULL) {
7830 GList *alist = amenu->next;
7832 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7833 if (systemid != NULL) {
7834 if (strcmp(systemid, compose->privacy_system) == 0 &&
7835 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7836 menuitem = GTK_WIDGET(amenu->data);
7838 can_sign = privacy_system_can_sign(systemid);
7839 can_encrypt = privacy_system_can_encrypt(systemid);
7843 } else if (strlen(compose->privacy_system) == 0 &&
7844 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7845 menuitem = GTK_WIDGET(amenu->data);
7848 can_encrypt = FALSE;
7855 if (menuitem != NULL)
7856 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7858 if (warn && !found && strlen(compose->privacy_system)) {
7859 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7860 "will not be able to sign or encrypt this message."),
7861 compose->privacy_system);
7865 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7866 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7869 static void compose_set_out_encoding(Compose *compose)
7871 CharSet out_encoding;
7872 const gchar *branch = NULL;
7873 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7875 switch(out_encoding) {
7876 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7877 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7878 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7879 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7880 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7881 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7882 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7883 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7884 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7885 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7886 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7887 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7888 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7889 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7890 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7891 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7892 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7893 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7894 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7895 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7896 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7897 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7898 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7899 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7900 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7901 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7902 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7903 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7904 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7905 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7906 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7907 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7909 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7912 static void compose_set_template_menu(Compose *compose)
7914 GSList *tmpl_list, *cur;
7918 tmpl_list = template_get_config();
7920 menu = gtk_menu_new();
7922 gtk_menu_set_accel_group (GTK_MENU (menu),
7923 gtk_ui_manager_get_accel_group(compose->ui_manager));
7924 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7925 Template *tmpl = (Template *)cur->data;
7926 gchar *accel_path = NULL;
7927 item = gtk_menu_item_new_with_label(tmpl->name);
7928 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7929 g_signal_connect(G_OBJECT(item), "activate",
7930 G_CALLBACK(compose_template_activate_cb),
7932 g_object_set_data(G_OBJECT(item), "template", tmpl);
7933 gtk_widget_show(item);
7934 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7935 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7939 gtk_widget_show(menu);
7940 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7943 void compose_update_actions_menu(Compose *compose)
7945 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7948 static void compose_update_privacy_systems_menu(Compose *compose)
7950 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7951 GSList *systems, *cur;
7953 GtkWidget *system_none;
7955 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7956 GtkWidget *privacy_menu = gtk_menu_new();
7958 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7959 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7961 g_signal_connect(G_OBJECT(system_none), "activate",
7962 G_CALLBACK(compose_set_privacy_system_cb), compose);
7964 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7965 gtk_widget_show(system_none);
7967 systems = privacy_get_system_ids();
7968 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7969 gchar *systemid = cur->data;
7971 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7972 widget = gtk_radio_menu_item_new_with_label(group,
7973 privacy_system_get_name(systemid));
7974 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7975 g_strdup(systemid), g_free);
7976 g_signal_connect(G_OBJECT(widget), "activate",
7977 G_CALLBACK(compose_set_privacy_system_cb), compose);
7979 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
7980 gtk_widget_show(widget);
7983 g_slist_free(systems);
7984 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
7985 gtk_widget_show_all(privacy_menu);
7986 gtk_widget_show_all(privacy_menuitem);
7989 void compose_reflect_prefs_all(void)
7994 for (cur = compose_list; cur != NULL; cur = cur->next) {
7995 compose = (Compose *)cur->data;
7996 compose_set_template_menu(compose);
8000 void compose_reflect_prefs_pixmap_theme(void)
8005 for (cur = compose_list; cur != NULL; cur = cur->next) {
8006 compose = (Compose *)cur->data;
8007 toolbar_update(TOOLBAR_COMPOSE, compose);
8011 static const gchar *compose_quote_char_from_context(Compose *compose)
8013 const gchar *qmark = NULL;
8015 cm_return_val_if_fail(compose != NULL, NULL);
8017 switch (compose->mode) {
8018 /* use forward-specific quote char */
8019 case COMPOSE_FORWARD:
8020 case COMPOSE_FORWARD_AS_ATTACH:
8021 case COMPOSE_FORWARD_INLINE:
8022 if (compose->folder && compose->folder->prefs &&
8023 compose->folder->prefs->forward_with_format)
8024 qmark = compose->folder->prefs->forward_quotemark;
8025 else if (compose->account->forward_with_format)
8026 qmark = compose->account->forward_quotemark;
8028 qmark = prefs_common.fw_quotemark;
8031 /* use reply-specific quote char in all other modes */
8033 if (compose->folder && compose->folder->prefs &&
8034 compose->folder->prefs->reply_with_format)
8035 qmark = compose->folder->prefs->reply_quotemark;
8036 else if (compose->account->reply_with_format)
8037 qmark = compose->account->reply_quotemark;
8039 qmark = prefs_common.quotemark;
8043 if (qmark == NULL || *qmark == '\0')
8049 static void compose_template_apply(Compose *compose, Template *tmpl,
8053 GtkTextBuffer *buffer;
8057 gchar *parsed_str = NULL;
8058 gint cursor_pos = 0;
8059 const gchar *err_msg = _("The body of the template has an error at line %d.");
8062 /* process the body */
8064 text = GTK_TEXT_VIEW(compose->text);
8065 buffer = gtk_text_view_get_buffer(text);
8068 qmark = compose_quote_char_from_context(compose);
8070 if (compose->replyinfo != NULL) {
8073 gtk_text_buffer_set_text(buffer, "", -1);
8074 mark = gtk_text_buffer_get_insert(buffer);
8075 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8077 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8078 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8080 } else if (compose->fwdinfo != NULL) {
8083 gtk_text_buffer_set_text(buffer, "", -1);
8084 mark = gtk_text_buffer_get_insert(buffer);
8085 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8087 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8088 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8091 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8093 GtkTextIter start, end;
8096 gtk_text_buffer_get_start_iter(buffer, &start);
8097 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8098 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8100 /* clear the buffer now */
8102 gtk_text_buffer_set_text(buffer, "", -1);
8104 parsed_str = compose_quote_fmt(compose, dummyinfo,
8105 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8106 procmsg_msginfo_free( dummyinfo );
8112 gtk_text_buffer_set_text(buffer, "", -1);
8113 mark = gtk_text_buffer_get_insert(buffer);
8114 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8117 if (replace && parsed_str && compose->account->auto_sig)
8118 compose_insert_sig(compose, FALSE);
8120 if (replace && parsed_str) {
8121 gtk_text_buffer_get_start_iter(buffer, &iter);
8122 gtk_text_buffer_place_cursor(buffer, &iter);
8126 cursor_pos = quote_fmt_get_cursor_pos();
8127 compose->set_cursor_pos = cursor_pos;
8128 if (cursor_pos == -1)
8130 gtk_text_buffer_get_start_iter(buffer, &iter);
8131 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8132 gtk_text_buffer_place_cursor(buffer, &iter);
8135 /* process the other fields */
8137 compose_template_apply_fields(compose, tmpl);
8138 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8139 quote_fmt_reset_vartable();
8140 compose_changed_cb(NULL, compose);
8143 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8144 gtkaspell_highlight_all(compose->gtkaspell);
8148 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8150 MsgInfo* dummyinfo = NULL;
8151 MsgInfo *msginfo = NULL;
8154 if (compose->replyinfo != NULL)
8155 msginfo = compose->replyinfo;
8156 else if (compose->fwdinfo != NULL)
8157 msginfo = compose->fwdinfo;
8159 dummyinfo = compose_msginfo_new_from_compose(compose);
8160 msginfo = dummyinfo;
8163 if (tmpl->from && *tmpl->from != '\0') {
8165 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8166 compose->gtkaspell);
8168 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8170 quote_fmt_scan_string(tmpl->from);
8173 buf = quote_fmt_get_buffer();
8175 alertpanel_error(_("Template From format error."));
8177 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8181 if (tmpl->to && *tmpl->to != '\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->to);
8191 buf = quote_fmt_get_buffer();
8193 alertpanel_error(_("Template To format error."));
8195 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8199 if (tmpl->cc && *tmpl->cc != '\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->cc);
8209 buf = quote_fmt_get_buffer();
8211 alertpanel_error(_("Template Cc format error."));
8213 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8217 if (tmpl->bcc && *tmpl->bcc != '\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->bcc);
8227 buf = quote_fmt_get_buffer();
8229 alertpanel_error(_("Template Bcc format error."));
8231 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8235 /* process the subject */
8236 if (tmpl->subject && *tmpl->subject != '\0') {
8238 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8239 compose->gtkaspell);
8241 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8243 quote_fmt_scan_string(tmpl->subject);
8246 buf = quote_fmt_get_buffer();
8248 alertpanel_error(_("Template subject format error."));
8250 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8254 procmsg_msginfo_free( dummyinfo );
8257 static void compose_destroy(Compose *compose)
8259 GtkTextBuffer *buffer;
8260 GtkClipboard *clipboard;
8262 compose_list = g_list_remove(compose_list, compose);
8264 if (compose->updating) {
8265 debug_print("danger, not destroying anything now\n");
8266 compose->deferred_destroy = TRUE;
8269 /* NOTE: address_completion_end() does nothing with the window
8270 * however this may change. */
8271 address_completion_end(compose->window);
8273 slist_free_strings(compose->to_list);
8274 g_slist_free(compose->to_list);
8275 slist_free_strings(compose->newsgroup_list);
8276 g_slist_free(compose->newsgroup_list);
8277 slist_free_strings(compose->header_list);
8278 g_slist_free(compose->header_list);
8280 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8282 g_hash_table_destroy(compose->email_hashtable);
8284 procmsg_msginfo_free(compose->targetinfo);
8285 procmsg_msginfo_free(compose->replyinfo);
8286 procmsg_msginfo_free(compose->fwdinfo);
8288 g_free(compose->replyto);
8289 g_free(compose->cc);
8290 g_free(compose->bcc);
8291 g_free(compose->newsgroups);
8292 g_free(compose->followup_to);
8294 g_free(compose->ml_post);
8296 g_free(compose->inreplyto);
8297 g_free(compose->references);
8298 g_free(compose->msgid);
8299 g_free(compose->boundary);
8301 g_free(compose->redirect_filename);
8302 if (compose->undostruct)
8303 undo_destroy(compose->undostruct);
8305 g_free(compose->sig_str);
8307 g_free(compose->exteditor_file);
8309 g_free(compose->orig_charset);
8311 g_free(compose->privacy_system);
8313 if (addressbook_get_target_compose() == compose)
8314 addressbook_set_target_compose(NULL);
8317 if (compose->gtkaspell) {
8318 gtkaspell_delete(compose->gtkaspell);
8319 compose->gtkaspell = NULL;
8323 if (!compose->batch) {
8324 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8325 prefs_common.compose_height = compose->window->allocation.height;
8328 if (!gtk_widget_get_parent(compose->paned))
8329 gtk_widget_destroy(compose->paned);
8330 gtk_widget_destroy(compose->popupmenu);
8332 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8333 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8334 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8336 gtk_widget_destroy(compose->window);
8337 toolbar_destroy(compose->toolbar);
8338 g_free(compose->toolbar);
8339 g_mutex_free(compose->mutex);
8343 static void compose_attach_info_free(AttachInfo *ainfo)
8345 g_free(ainfo->file);
8346 g_free(ainfo->content_type);
8347 g_free(ainfo->name);
8351 static void compose_attach_update_label(Compose *compose)
8356 GtkTreeModel *model;
8361 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8362 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8363 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8367 while(gtk_tree_model_iter_next(model, &iter))
8370 text = g_strdup_printf("(%d)", i);
8371 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8375 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8377 Compose *compose = (Compose *)data;
8378 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8379 GtkTreeSelection *selection;
8381 GtkTreeModel *model;
8383 selection = gtk_tree_view_get_selection(tree_view);
8384 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8389 for (cur = sel; cur != NULL; cur = cur->next) {
8390 GtkTreePath *path = cur->data;
8391 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8394 gtk_tree_path_free(path);
8397 for (cur = sel; cur != NULL; cur = cur->next) {
8398 GtkTreeRowReference *ref = cur->data;
8399 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8402 if (gtk_tree_model_get_iter(model, &iter, path))
8403 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8405 gtk_tree_path_free(path);
8406 gtk_tree_row_reference_free(ref);
8410 compose_attach_update_label(compose);
8413 static struct _AttachProperty
8416 GtkWidget *mimetype_entry;
8417 GtkWidget *encoding_optmenu;
8418 GtkWidget *path_entry;
8419 GtkWidget *filename_entry;
8421 GtkWidget *cancel_btn;
8424 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8426 gtk_tree_path_free((GtkTreePath *)ptr);
8429 static void compose_attach_property(GtkAction *action, gpointer data)
8431 Compose *compose = (Compose *)data;
8432 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8434 GtkComboBox *optmenu;
8435 GtkTreeSelection *selection;
8437 GtkTreeModel *model;
8440 static gboolean cancelled;
8442 /* only if one selected */
8443 selection = gtk_tree_view_get_selection(tree_view);
8444 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8447 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8451 path = (GtkTreePath *) sel->data;
8452 gtk_tree_model_get_iter(model, &iter, path);
8453 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8456 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8462 if (!attach_prop.window)
8463 compose_attach_property_create(&cancelled);
8464 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8465 gtk_widget_grab_focus(attach_prop.ok_btn);
8466 gtk_widget_show(attach_prop.window);
8467 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8469 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8470 if (ainfo->encoding == ENC_UNKNOWN)
8471 combobox_select_by_data(optmenu, ENC_BASE64);
8473 combobox_select_by_data(optmenu, ainfo->encoding);
8475 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8476 ainfo->content_type ? ainfo->content_type : "");
8477 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8478 ainfo->file ? ainfo->file : "");
8479 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8480 ainfo->name ? ainfo->name : "");
8483 const gchar *entry_text;
8485 gchar *cnttype = NULL;
8492 gtk_widget_hide(attach_prop.window);
8493 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8498 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8499 if (*entry_text != '\0') {
8502 text = g_strstrip(g_strdup(entry_text));
8503 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8504 cnttype = g_strdup(text);
8507 alertpanel_error(_("Invalid MIME type."));
8513 ainfo->encoding = combobox_get_active_data(optmenu);
8515 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8516 if (*entry_text != '\0') {
8517 if (is_file_exist(entry_text) &&
8518 (size = get_file_size(entry_text)) > 0)
8519 file = g_strdup(entry_text);
8522 (_("File doesn't exist or is empty."));
8528 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8529 if (*entry_text != '\0') {
8530 g_free(ainfo->name);
8531 ainfo->name = g_strdup(entry_text);
8535 g_free(ainfo->content_type);
8536 ainfo->content_type = cnttype;
8539 g_free(ainfo->file);
8543 ainfo->size = (goffset)size;
8545 /* update tree store */
8546 text = to_human_readable(ainfo->size);
8547 gtk_tree_model_get_iter(model, &iter, path);
8548 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8549 COL_MIMETYPE, ainfo->content_type,
8551 COL_NAME, ainfo->name,
8557 gtk_tree_path_free(path);
8560 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8562 label = gtk_label_new(str); \
8563 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8564 GTK_FILL, 0, 0, 0); \
8565 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8567 entry = gtk_entry_new(); \
8568 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8569 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8572 static void compose_attach_property_create(gboolean *cancelled)
8578 GtkWidget *mimetype_entry;
8581 GtkListStore *optmenu_menu;
8582 GtkWidget *path_entry;
8583 GtkWidget *filename_entry;
8586 GtkWidget *cancel_btn;
8587 GList *mime_type_list, *strlist;
8590 debug_print("Creating attach_property window...\n");
8592 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8593 gtk_widget_set_size_request(window, 480, -1);
8594 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8595 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8596 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8597 g_signal_connect(G_OBJECT(window), "delete_event",
8598 G_CALLBACK(attach_property_delete_event),
8600 g_signal_connect(G_OBJECT(window), "key_press_event",
8601 G_CALLBACK(attach_property_key_pressed),
8604 vbox = gtk_vbox_new(FALSE, 8);
8605 gtk_container_add(GTK_CONTAINER(window), vbox);
8607 table = gtk_table_new(4, 2, FALSE);
8608 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8609 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8610 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8612 label = gtk_label_new(_("MIME type"));
8613 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8615 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8616 mimetype_entry = gtk_combo_box_entry_new_text();
8617 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8618 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8620 /* stuff with list */
8621 mime_type_list = procmime_get_mime_type_list();
8623 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8624 MimeType *type = (MimeType *) mime_type_list->data;
8627 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8629 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8632 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8633 (GCompareFunc)strcmp2);
8636 for (mime_type_list = strlist; mime_type_list != NULL;
8637 mime_type_list = mime_type_list->next) {
8638 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8639 g_free(mime_type_list->data);
8641 g_list_free(strlist);
8642 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8643 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8645 label = gtk_label_new(_("Encoding"));
8646 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8648 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8650 hbox = gtk_hbox_new(FALSE, 0);
8651 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8652 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8654 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8655 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8657 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8658 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8659 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8660 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8661 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8663 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8665 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8666 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8668 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8669 &ok_btn, GTK_STOCK_OK,
8671 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8672 gtk_widget_grab_default(ok_btn);
8674 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8675 G_CALLBACK(attach_property_ok),
8677 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8678 G_CALLBACK(attach_property_cancel),
8681 gtk_widget_show_all(vbox);
8683 attach_prop.window = window;
8684 attach_prop.mimetype_entry = mimetype_entry;
8685 attach_prop.encoding_optmenu = optmenu;
8686 attach_prop.path_entry = path_entry;
8687 attach_prop.filename_entry = filename_entry;
8688 attach_prop.ok_btn = ok_btn;
8689 attach_prop.cancel_btn = cancel_btn;
8692 #undef SET_LABEL_AND_ENTRY
8694 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8700 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8706 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8707 gboolean *cancelled)
8715 static gboolean attach_property_key_pressed(GtkWidget *widget,
8717 gboolean *cancelled)
8719 if (event && event->keyval == GDK_Escape) {
8723 if (event && event->keyval == GDK_Return) {
8731 static void compose_exec_ext_editor(Compose *compose)
8738 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8739 G_DIR_SEPARATOR, compose);
8741 if (pipe(pipe_fds) < 0) {
8747 if ((pid = fork()) < 0) {
8754 /* close the write side of the pipe */
8757 compose->exteditor_file = g_strdup(tmp);
8758 compose->exteditor_pid = pid;
8760 compose_set_ext_editor_sensitive(compose, FALSE);
8763 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8765 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8767 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8771 } else { /* process-monitoring process */
8777 /* close the read side of the pipe */
8780 if (compose_write_body_to_file(compose, tmp) < 0) {
8781 fd_write_all(pipe_fds[1], "2\n", 2);
8785 pid_ed = compose_exec_ext_editor_real(tmp);
8787 fd_write_all(pipe_fds[1], "1\n", 2);
8791 /* wait until editor is terminated */
8792 waitpid(pid_ed, NULL, 0);
8794 fd_write_all(pipe_fds[1], "0\n", 2);
8801 #endif /* G_OS_UNIX */
8805 static gint compose_exec_ext_editor_real(const gchar *file)
8812 cm_return_val_if_fail(file != NULL, -1);
8814 if ((pid = fork()) < 0) {
8819 if (pid != 0) return pid;
8821 /* grandchild process */
8823 if (setpgid(0, getppid()))
8826 if (prefs_common_get_ext_editor_cmd() &&
8827 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8828 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8829 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8831 if (prefs_common_get_ext_editor_cmd())
8832 g_warning("External editor command-line is invalid: '%s'\n",
8833 prefs_common_get_ext_editor_cmd());
8834 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8837 cmdline = strsplit_with_quote(buf, " ", 1024);
8838 execvp(cmdline[0], cmdline);
8841 g_strfreev(cmdline);
8846 static gboolean compose_ext_editor_kill(Compose *compose)
8848 pid_t pgid = compose->exteditor_pid * -1;
8851 ret = kill(pgid, 0);
8853 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8857 msg = g_strdup_printf
8858 (_("The external editor is still working.\n"
8859 "Force terminating the process?\n"
8860 "process group id: %d"), -pgid);
8861 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8862 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8866 if (val == G_ALERTALTERNATE) {
8867 g_source_remove(compose->exteditor_tag);
8868 g_io_channel_shutdown(compose->exteditor_ch,
8870 g_io_channel_unref(compose->exteditor_ch);
8872 if (kill(pgid, SIGTERM) < 0) perror("kill");
8873 waitpid(compose->exteditor_pid, NULL, 0);
8875 g_warning("Terminated process group id: %d", -pgid);
8876 g_warning("Temporary file: %s",
8877 compose->exteditor_file);
8879 compose_set_ext_editor_sensitive(compose, TRUE);
8881 g_free(compose->exteditor_file);
8882 compose->exteditor_file = NULL;
8883 compose->exteditor_pid = -1;
8884 compose->exteditor_ch = NULL;
8885 compose->exteditor_tag = -1;
8893 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8897 Compose *compose = (Compose *)data;
8900 debug_print(_("Compose: input from monitoring process\n"));
8902 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8904 g_io_channel_shutdown(source, FALSE, NULL);
8905 g_io_channel_unref(source);
8907 waitpid(compose->exteditor_pid, NULL, 0);
8909 if (buf[0] == '0') { /* success */
8910 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8911 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8913 gtk_text_buffer_set_text(buffer, "", -1);
8914 compose_insert_file(compose, compose->exteditor_file);
8915 compose_changed_cb(NULL, compose);
8916 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8918 if (claws_unlink(compose->exteditor_file) < 0)
8919 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8920 } else if (buf[0] == '1') { /* failed */
8921 g_warning("Couldn't exec external editor\n");
8922 if (claws_unlink(compose->exteditor_file) < 0)
8923 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8924 } else if (buf[0] == '2') {
8925 g_warning("Couldn't write to file\n");
8926 } else if (buf[0] == '3') {
8927 g_warning("Pipe read failed\n");
8930 compose_set_ext_editor_sensitive(compose, TRUE);
8932 g_free(compose->exteditor_file);
8933 compose->exteditor_file = NULL;
8934 compose->exteditor_pid = -1;
8935 compose->exteditor_ch = NULL;
8936 compose->exteditor_tag = -1;
8941 static void compose_set_ext_editor_sensitive(Compose *compose,
8944 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8945 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8946 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8947 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8948 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8949 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8950 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8952 gtk_widget_set_sensitive(compose->text, sensitive);
8953 if (compose->toolbar->send_btn)
8954 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8955 if (compose->toolbar->sendl_btn)
8956 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8957 if (compose->toolbar->draft_btn)
8958 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8959 if (compose->toolbar->insert_btn)
8960 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8961 if (compose->toolbar->sig_btn)
8962 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8963 if (compose->toolbar->exteditor_btn)
8964 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8965 if (compose->toolbar->linewrap_current_btn)
8966 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8967 if (compose->toolbar->linewrap_all_btn)
8968 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8970 #endif /* G_OS_UNIX */
8973 * compose_undo_state_changed:
8975 * Change the sensivity of the menuentries undo and redo
8977 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8978 gint redo_state, gpointer data)
8980 Compose *compose = (Compose *)data;
8982 switch (undo_state) {
8983 case UNDO_STATE_TRUE:
8984 if (!undostruct->undo_state) {
8985 undostruct->undo_state = TRUE;
8986 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
8989 case UNDO_STATE_FALSE:
8990 if (undostruct->undo_state) {
8991 undostruct->undo_state = FALSE;
8992 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8995 case UNDO_STATE_UNCHANGED:
8997 case UNDO_STATE_REFRESH:
8998 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9001 g_warning("Undo state not recognized");
9005 switch (redo_state) {
9006 case UNDO_STATE_TRUE:
9007 if (!undostruct->redo_state) {
9008 undostruct->redo_state = TRUE;
9009 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9012 case UNDO_STATE_FALSE:
9013 if (undostruct->redo_state) {
9014 undostruct->redo_state = FALSE;
9015 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9018 case UNDO_STATE_UNCHANGED:
9020 case UNDO_STATE_REFRESH:
9021 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9024 g_warning("Redo state not recognized");
9029 /* callback functions */
9031 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9032 * includes "non-client" (windows-izm) in calculation, so this calculation
9033 * may not be accurate.
9035 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9036 GtkAllocation *allocation,
9037 GtkSHRuler *shruler)
9039 if (prefs_common.show_ruler) {
9040 gint char_width = 0, char_height = 0;
9041 gint line_width_in_chars;
9043 gtkut_get_font_size(GTK_WIDGET(widget),
9044 &char_width, &char_height);
9045 line_width_in_chars =
9046 (allocation->width - allocation->x) / char_width;
9048 /* got the maximum */
9049 gtk_ruler_set_range(GTK_RULER(shruler),
9050 0.0, line_width_in_chars, 0,
9051 /*line_width_in_chars*/ char_width);
9058 ComposeEntryType header;
9060 ComposePrefType type;
9061 gboolean entry_marked;
9064 static void account_activated(GtkComboBox *optmenu, gpointer data)
9066 Compose *compose = (Compose *)data;
9069 gchar *folderidentifier;
9070 gint account_id = 0;
9073 GSList *list, *saved_list = NULL;
9074 HeaderEntryState *state;
9075 GtkRcStyle *style = NULL;
9076 static GdkColor yellow;
9077 static gboolean color_set = FALSE;
9079 /* Get ID of active account in the combo box */
9080 menu = gtk_combo_box_get_model(optmenu);
9081 gtk_combo_box_get_active_iter(optmenu, &iter);
9082 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9084 ac = account_find_from_id(account_id);
9085 cm_return_if_fail(ac != NULL);
9087 if (ac != compose->account) {
9088 compose_select_account(compose, ac, FALSE);
9090 for (list = compose->header_list; list; list = list->next) {
9091 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9093 if (hentry->type == PREF_ACCOUNT || !list->next) {
9094 compose_destroy_headerentry(compose, hentry);
9098 state = g_malloc0(sizeof(HeaderEntryState));
9099 state->header = combobox_get_active_data(
9100 GTK_COMBO_BOX(hentry->combo));
9101 state->entry = gtk_editable_get_chars(
9102 GTK_EDITABLE(hentry->entry), 0, -1);
9103 state->type = hentry->type;
9106 gdk_color_parse("#f5f6be", &yellow);
9107 color_set = gdk_colormap_alloc_color(
9108 gdk_colormap_get_system(),
9109 &yellow, FALSE, TRUE);
9112 style = gtk_widget_get_modifier_style(hentry->entry);
9113 state->entry_marked = gdk_color_equal(&yellow,
9114 &style->base[GTK_STATE_NORMAL]);
9116 saved_list = g_slist_append(saved_list, state);
9117 compose_destroy_headerentry(compose, hentry);
9120 compose->header_last = NULL;
9121 g_slist_free(compose->header_list);
9122 compose->header_list = NULL;
9123 compose->header_nextrow = 1;
9124 compose_create_header_entry(compose);
9126 if (ac->set_autocc && ac->auto_cc)
9127 compose_entry_append(compose, ac->auto_cc,
9128 COMPOSE_CC, PREF_ACCOUNT);
9130 if (ac->set_autobcc && ac->auto_bcc)
9131 compose_entry_append(compose, ac->auto_bcc,
9132 COMPOSE_BCC, PREF_ACCOUNT);
9134 if (ac->set_autoreplyto && ac->auto_replyto)
9135 compose_entry_append(compose, ac->auto_replyto,
9136 COMPOSE_REPLYTO, PREF_ACCOUNT);
9138 for (list = saved_list; list; list = list->next) {
9139 state = (HeaderEntryState *) list->data;
9141 compose_entry_append(compose, state->entry,
9142 state->header, state->type);
9143 if (state->entry_marked)
9144 compose_entry_mark_default_to(compose, state->entry);
9146 g_free(state->entry);
9148 g_slist_free(saved_list);
9150 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9151 (ac->protocol == A_NNTP) ?
9152 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9155 /* Set message save folder */
9156 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9157 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9159 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9160 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9162 compose_set_save_to(compose, NULL);
9163 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9164 folderidentifier = folder_item_get_identifier(account_get_special_folder
9165 (compose->account, F_OUTBOX));
9166 compose_set_save_to(compose, folderidentifier);
9167 g_free(folderidentifier);
9171 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9172 GtkTreeViewColumn *column, Compose *compose)
9174 compose_attach_property(NULL, compose);
9177 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9180 Compose *compose = (Compose *)data;
9181 GtkTreeSelection *attach_selection;
9182 gint attach_nr_selected;
9184 if (!event) return FALSE;
9186 if (event->button == 3) {
9187 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9188 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9190 if (attach_nr_selected > 0)
9192 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9193 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9195 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9196 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9199 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9200 NULL, NULL, event->button, event->time);
9207 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9210 Compose *compose = (Compose *)data;
9212 if (!event) return FALSE;
9214 switch (event->keyval) {
9216 compose_attach_remove_selected(NULL, compose);
9222 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9224 toolbar_comp_set_sensitive(compose, allow);
9225 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9226 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9228 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9230 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9231 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9232 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9234 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9238 static void compose_send_cb(GtkAction *action, gpointer data)
9240 Compose *compose = (Compose *)data;
9242 if (prefs_common.work_offline &&
9243 !inc_offline_should_override(TRUE,
9244 _("Claws Mail needs network access in order "
9245 "to send this email.")))
9248 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9249 g_source_remove(compose->draft_timeout_tag);
9250 compose->draft_timeout_tag = -1;
9253 compose_send(compose);
9256 static void compose_send_later_cb(GtkAction *action, gpointer data)
9258 Compose *compose = (Compose *)data;
9262 compose_allow_user_actions(compose, FALSE);
9263 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9264 compose_allow_user_actions(compose, TRUE);
9268 compose_close(compose);
9269 } else if (val == -1) {
9270 alertpanel_error(_("Could not queue message."));
9271 } else if (val == -2) {
9272 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9273 } else if (val == -3) {
9274 if (privacy_peek_error())
9275 alertpanel_error(_("Could not queue message for sending:\n\n"
9276 "Signature failed: %s"), privacy_get_error());
9277 } else if (val == -4) {
9278 alertpanel_error(_("Could not queue message for sending:\n\n"
9279 "Charset conversion failed."));
9280 } else if (val == -5) {
9281 alertpanel_error(_("Could not queue message for sending:\n\n"
9282 "Couldn't get recipient encryption key."));
9283 } else if (val == -6) {
9286 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9289 #define DRAFTED_AT_EXIT "drafted_at_exit"
9290 static void compose_register_draft(MsgInfo *info)
9292 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9293 DRAFTED_AT_EXIT, NULL);
9294 FILE *fp = g_fopen(filepath, "ab");
9297 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9305 gboolean compose_draft (gpointer data, guint action)
9307 Compose *compose = (Compose *)data;
9311 MsgFlags flag = {0, 0};
9312 static gboolean lock = FALSE;
9313 MsgInfo *newmsginfo;
9315 gboolean target_locked = FALSE;
9316 gboolean err = FALSE;
9318 if (lock) return FALSE;
9320 if (compose->sending)
9323 draft = account_get_special_folder(compose->account, F_DRAFT);
9324 cm_return_val_if_fail(draft != NULL, FALSE);
9326 if (!g_mutex_trylock(compose->mutex)) {
9327 /* we don't want to lock the mutex once it's available,
9328 * because as the only other part of compose.c locking
9329 * it is compose_close - which means once unlocked,
9330 * the compose struct will be freed */
9331 debug_print("couldn't lock mutex, probably sending\n");
9337 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9338 G_DIR_SEPARATOR, compose);
9339 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9340 FILE_OP_ERROR(tmp, "fopen");
9344 /* chmod for security */
9345 if (change_file_mode_rw(fp, tmp) < 0) {
9346 FILE_OP_ERROR(tmp, "chmod");
9347 g_warning("can't change file mode\n");
9350 /* Save draft infos */
9351 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9352 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9354 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9355 gchar *savefolderid;
9357 savefolderid = compose_get_save_to(compose);
9358 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9359 g_free(savefolderid);
9361 if (compose->return_receipt) {
9362 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9364 if (compose->privacy_system) {
9365 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9366 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9367 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9370 /* Message-ID of message replying to */
9371 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9374 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9375 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9378 /* Message-ID of message forwarding to */
9379 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9382 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9383 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9387 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9388 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9390 /* end of headers */
9391 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9398 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9402 if (fclose(fp) == EOF) {
9406 if (compose->targetinfo) {
9407 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9408 flag.perm_flags = target_locked?MSG_LOCKED:0;
9410 flag.tmp_flags = MSG_DRAFT;
9412 folder_item_scan(draft);
9413 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9414 MsgInfo *tmpinfo = NULL;
9415 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9416 if (compose->msgid) {
9417 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9420 msgnum = tmpinfo->msgnum;
9421 procmsg_msginfo_free(tmpinfo);
9422 debug_print("got draft msgnum %d from scanning\n", msgnum);
9424 debug_print("didn't get draft msgnum after scanning\n");
9427 debug_print("got draft msgnum %d from adding\n", msgnum);
9433 if (action != COMPOSE_AUTO_SAVE) {
9434 if (action != COMPOSE_DRAFT_FOR_EXIT)
9435 alertpanel_error(_("Could not save draft."));
9438 gtkut_window_popup(compose->window);
9439 val = alertpanel_full(_("Could not save draft"),
9440 _("Could not save draft.\n"
9441 "Do you want to cancel exit or discard this email?"),
9442 _("_Cancel exit"), _("_Discard email"), NULL,
9443 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9444 if (val == G_ALERTALTERNATE) {
9446 g_mutex_unlock(compose->mutex); /* must be done before closing */
9447 compose_close(compose);
9451 g_mutex_unlock(compose->mutex); /* must be done before closing */
9460 if (compose->mode == COMPOSE_REEDIT) {
9461 compose_remove_reedit_target(compose, TRUE);
9464 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9467 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9469 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9471 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9472 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9473 procmsg_msginfo_set_flags(newmsginfo, 0,
9474 MSG_HAS_ATTACHMENT);
9476 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9477 compose_register_draft(newmsginfo);
9479 procmsg_msginfo_free(newmsginfo);
9482 folder_item_scan(draft);
9484 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9486 g_mutex_unlock(compose->mutex); /* must be done before closing */
9487 compose_close(compose);
9493 path = folder_item_fetch_msg(draft, msgnum);
9495 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9498 if (g_stat(path, &s) < 0) {
9499 FILE_OP_ERROR(path, "stat");
9505 procmsg_msginfo_free(compose->targetinfo);
9506 compose->targetinfo = procmsg_msginfo_new();
9507 compose->targetinfo->msgnum = msgnum;
9508 compose->targetinfo->size = (goffset)s.st_size;
9509 compose->targetinfo->mtime = s.st_mtime;
9510 compose->targetinfo->folder = draft;
9512 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9513 compose->mode = COMPOSE_REEDIT;
9515 if (action == COMPOSE_AUTO_SAVE) {
9516 compose->autosaved_draft = compose->targetinfo;
9518 compose->modified = FALSE;
9519 compose_set_title(compose);
9523 g_mutex_unlock(compose->mutex);
9527 void compose_clear_exit_drafts(void)
9529 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9530 DRAFTED_AT_EXIT, NULL);
9531 if (is_file_exist(filepath))
9532 claws_unlink(filepath);
9537 void compose_reopen_exit_drafts(void)
9539 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9540 DRAFTED_AT_EXIT, NULL);
9541 FILE *fp = g_fopen(filepath, "rb");
9545 while (fgets(buf, sizeof(buf), fp)) {
9546 gchar **parts = g_strsplit(buf, "\t", 2);
9547 const gchar *folder = parts[0];
9548 int msgnum = parts[1] ? atoi(parts[1]):-1;
9550 if (folder && *folder && msgnum > -1) {
9551 FolderItem *item = folder_find_item_from_identifier(folder);
9552 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9554 compose_reedit(info, FALSE);
9561 compose_clear_exit_drafts();
9564 static void compose_save_cb(GtkAction *action, gpointer data)
9566 Compose *compose = (Compose *)data;
9567 compose_draft(compose, COMPOSE_KEEP_EDITING);
9568 compose->rmode = COMPOSE_REEDIT;
9571 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9573 if (compose && file_list) {
9576 for ( tmp = file_list; tmp; tmp = tmp->next) {
9577 gchar *file = (gchar *) tmp->data;
9578 gchar *utf8_filename = conv_filename_to_utf8(file);
9579 compose_attach_append(compose, file, utf8_filename, NULL);
9580 compose_changed_cb(NULL, compose);
9585 g_free(utf8_filename);
9590 static void compose_attach_cb(GtkAction *action, gpointer data)
9592 Compose *compose = (Compose *)data;
9595 if (compose->redirect_filename != NULL)
9598 file_list = filesel_select_multiple_files_open(_("Select file"));
9601 compose_attach_from_list(compose, file_list, TRUE);
9602 g_list_free(file_list);
9606 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9608 Compose *compose = (Compose *)data;
9610 gint files_inserted = 0;
9612 file_list = filesel_select_multiple_files_open(_("Select file"));
9617 for ( tmp = file_list; tmp; tmp = tmp->next) {
9618 gchar *file = (gchar *) tmp->data;
9619 gchar *filedup = g_strdup(file);
9620 gchar *shortfile = g_path_get_basename(filedup);
9621 ComposeInsertResult res;
9622 /* insert the file if the file is short or if the user confirmed that
9623 he/she wants to insert the large file */
9624 res = compose_insert_file(compose, file);
9625 if (res == COMPOSE_INSERT_READ_ERROR) {
9626 alertpanel_error(_("File '%s' could not be read."), shortfile);
9627 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9628 alertpanel_error(_("File '%s' contained invalid characters\n"
9629 "for the current encoding, insertion may be incorrect."),
9631 } else if (res == COMPOSE_INSERT_SUCCESS)
9638 g_list_free(file_list);
9642 if (files_inserted > 0 && compose->gtkaspell &&
9643 compose->gtkaspell->check_while_typing)
9644 gtkaspell_highlight_all(compose->gtkaspell);
9648 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9650 Compose *compose = (Compose *)data;
9652 compose_insert_sig(compose, FALSE);
9655 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9659 Compose *compose = (Compose *)data;
9661 gtkut_widget_get_uposition(widget, &x, &y);
9662 if (!compose->batch) {
9663 prefs_common.compose_x = x;
9664 prefs_common.compose_y = y;
9666 if (compose->sending || compose->updating)
9668 compose_close_cb(NULL, compose);
9672 void compose_close_toolbar(Compose *compose)
9674 compose_close_cb(NULL, compose);
9677 static void compose_close_cb(GtkAction *action, gpointer data)
9679 Compose *compose = (Compose *)data;
9683 if (compose->exteditor_tag != -1) {
9684 if (!compose_ext_editor_kill(compose))
9689 if (compose->modified) {
9690 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9691 if (!g_mutex_trylock(compose->mutex)) {
9692 /* we don't want to lock the mutex once it's available,
9693 * because as the only other part of compose.c locking
9694 * it is compose_close - which means once unlocked,
9695 * the compose struct will be freed */
9696 debug_print("couldn't lock mutex, probably sending\n");
9700 val = alertpanel(_("Discard message"),
9701 _("This message has been modified. Discard it?"),
9702 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9704 val = alertpanel(_("Save changes"),
9705 _("This message has been modified. Save the latest changes?"),
9706 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9708 g_mutex_unlock(compose->mutex);
9710 case G_ALERTDEFAULT:
9711 if (prefs_common.autosave && !reedit)
9712 compose_remove_draft(compose);
9714 case G_ALERTALTERNATE:
9715 compose_draft(data, COMPOSE_QUIT_EDITING);
9722 compose_close(compose);
9725 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9727 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9728 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9729 Compose *compose = (Compose *) data;
9732 compose->out_encoding = (CharSet)value;
9735 static void compose_address_cb(GtkAction *action, gpointer data)
9737 Compose *compose = (Compose *)data;
9739 addressbook_open(compose);
9742 static void about_show_cb(GtkAction *action, gpointer data)
9747 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9749 Compose *compose = (Compose *)data;
9754 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9755 cm_return_if_fail(tmpl != NULL);
9757 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9759 val = alertpanel(_("Apply template"), msg,
9760 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9763 if (val == G_ALERTDEFAULT)
9764 compose_template_apply(compose, tmpl, TRUE);
9765 else if (val == G_ALERTALTERNATE)
9766 compose_template_apply(compose, tmpl, FALSE);
9769 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9771 Compose *compose = (Compose *)data;
9773 compose_exec_ext_editor(compose);
9776 static void compose_undo_cb(GtkAction *action, gpointer data)
9778 Compose *compose = (Compose *)data;
9779 gboolean prev_autowrap = compose->autowrap;
9781 compose->autowrap = FALSE;
9782 undo_undo(compose->undostruct);
9783 compose->autowrap = prev_autowrap;
9786 static void compose_redo_cb(GtkAction *action, gpointer data)
9788 Compose *compose = (Compose *)data;
9789 gboolean prev_autowrap = compose->autowrap;
9791 compose->autowrap = FALSE;
9792 undo_redo(compose->undostruct);
9793 compose->autowrap = prev_autowrap;
9796 static void entry_cut_clipboard(GtkWidget *entry)
9798 if (GTK_IS_EDITABLE(entry))
9799 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9800 else if (GTK_IS_TEXT_VIEW(entry))
9801 gtk_text_buffer_cut_clipboard(
9802 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9803 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9807 static void entry_copy_clipboard(GtkWidget *entry)
9809 if (GTK_IS_EDITABLE(entry))
9810 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9811 else if (GTK_IS_TEXT_VIEW(entry))
9812 gtk_text_buffer_copy_clipboard(
9813 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9814 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9817 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9818 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9820 if (GTK_IS_TEXT_VIEW(entry)) {
9821 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9822 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9823 GtkTextIter start_iter, end_iter;
9825 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9827 if (contents == NULL)
9830 /* we shouldn't delete the selection when middle-click-pasting, or we
9831 * can't mid-click-paste our own selection */
9832 if (clip != GDK_SELECTION_PRIMARY) {
9833 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9834 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9837 if (insert_place == NULL) {
9838 /* if insert_place isn't specified, insert at the cursor.
9839 * used for Ctrl-V pasting */
9840 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9841 start = gtk_text_iter_get_offset(&start_iter);
9842 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9844 /* if insert_place is specified, paste here.
9845 * used for mid-click-pasting */
9846 start = gtk_text_iter_get_offset(insert_place);
9847 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9848 if (prefs_common.primary_paste_unselects)
9849 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9853 /* paste unwrapped: mark the paste so it's not wrapped later */
9854 end = start + strlen(contents);
9855 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9856 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9857 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9858 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9859 /* rewrap paragraph now (after a mid-click-paste) */
9860 mark_start = gtk_text_buffer_get_insert(buffer);
9861 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9862 gtk_text_iter_backward_char(&start_iter);
9863 compose_beautify_paragraph(compose, &start_iter, TRUE);
9865 } else if (GTK_IS_EDITABLE(entry))
9866 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9868 compose->modified = TRUE;
9871 static void entry_allsel(GtkWidget *entry)
9873 if (GTK_IS_EDITABLE(entry))
9874 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9875 else if (GTK_IS_TEXT_VIEW(entry)) {
9876 GtkTextIter startiter, enditer;
9877 GtkTextBuffer *textbuf;
9879 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9880 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9881 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9883 gtk_text_buffer_move_mark_by_name(textbuf,
9884 "selection_bound", &startiter);
9885 gtk_text_buffer_move_mark_by_name(textbuf,
9886 "insert", &enditer);
9890 static void compose_cut_cb(GtkAction *action, gpointer data)
9892 Compose *compose = (Compose *)data;
9893 if (compose->focused_editable
9894 #ifndef GENERIC_UMPC
9895 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9898 entry_cut_clipboard(compose->focused_editable);
9901 static void compose_copy_cb(GtkAction *action, gpointer data)
9903 Compose *compose = (Compose *)data;
9904 if (compose->focused_editable
9905 #ifndef GENERIC_UMPC
9906 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9909 entry_copy_clipboard(compose->focused_editable);
9912 static void compose_paste_cb(GtkAction *action, gpointer data)
9914 Compose *compose = (Compose *)data;
9916 GtkTextBuffer *buffer;
9918 if (compose->focused_editable &&
9919 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9920 entry_paste_clipboard(compose, compose->focused_editable,
9921 prefs_common.linewrap_pastes,
9922 GDK_SELECTION_CLIPBOARD, NULL);
9926 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9927 compose->gtkaspell &&
9928 compose->gtkaspell->check_while_typing)
9929 gtkaspell_highlight_all(compose->gtkaspell);
9933 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9935 Compose *compose = (Compose *)data;
9936 gint wrap_quote = prefs_common.linewrap_quote;
9937 if (compose->focused_editable
9938 #ifndef GENERIC_UMPC
9939 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9942 /* let text_insert() (called directly or at a later time
9943 * after the gtk_editable_paste_clipboard) know that
9944 * text is to be inserted as a quotation. implemented
9945 * by using a simple refcount... */
9946 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9947 G_OBJECT(compose->focused_editable),
9948 "paste_as_quotation"));
9949 g_object_set_data(G_OBJECT(compose->focused_editable),
9950 "paste_as_quotation",
9951 GINT_TO_POINTER(paste_as_quotation + 1));
9952 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9953 entry_paste_clipboard(compose, compose->focused_editable,
9954 prefs_common.linewrap_pastes,
9955 GDK_SELECTION_CLIPBOARD, NULL);
9956 prefs_common.linewrap_quote = wrap_quote;
9960 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9962 Compose *compose = (Compose *)data;
9964 GtkTextBuffer *buffer;
9966 if (compose->focused_editable
9967 #ifndef GENERIC_UMPC
9968 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9971 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9972 GDK_SELECTION_CLIPBOARD, NULL);
9976 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9977 compose->gtkaspell &&
9978 compose->gtkaspell->check_while_typing)
9979 gtkaspell_highlight_all(compose->gtkaspell);
9983 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
9985 Compose *compose = (Compose *)data;
9987 GtkTextBuffer *buffer;
9989 if (compose->focused_editable
9990 #ifndef GENERIC_UMPC
9991 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9994 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9995 GDK_SELECTION_CLIPBOARD, NULL);
9999 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
10000 compose->gtkaspell &&
10001 compose->gtkaspell->check_while_typing)
10002 gtkaspell_highlight_all(compose->gtkaspell);
10006 static void compose_allsel_cb(GtkAction *action, gpointer data)
10008 Compose *compose = (Compose *)data;
10009 if (compose->focused_editable
10010 #ifndef GENERIC_UMPC
10011 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
10014 entry_allsel(compose->focused_editable);
10017 static void textview_move_beginning_of_line (GtkTextView *text)
10019 GtkTextBuffer *buffer;
10023 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10025 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10026 mark = gtk_text_buffer_get_insert(buffer);
10027 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10028 gtk_text_iter_set_line_offset(&ins, 0);
10029 gtk_text_buffer_place_cursor(buffer, &ins);
10032 static void textview_move_forward_character (GtkTextView *text)
10034 GtkTextBuffer *buffer;
10038 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10040 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10041 mark = gtk_text_buffer_get_insert(buffer);
10042 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10043 if (gtk_text_iter_forward_cursor_position(&ins))
10044 gtk_text_buffer_place_cursor(buffer, &ins);
10047 static void textview_move_backward_character (GtkTextView *text)
10049 GtkTextBuffer *buffer;
10053 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10055 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10056 mark = gtk_text_buffer_get_insert(buffer);
10057 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10058 if (gtk_text_iter_backward_cursor_position(&ins))
10059 gtk_text_buffer_place_cursor(buffer, &ins);
10062 static void textview_move_forward_word (GtkTextView *text)
10064 GtkTextBuffer *buffer;
10069 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10071 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10072 mark = gtk_text_buffer_get_insert(buffer);
10073 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10074 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10075 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10076 gtk_text_iter_backward_word_start(&ins);
10077 gtk_text_buffer_place_cursor(buffer, &ins);
10081 static void textview_move_backward_word (GtkTextView *text)
10083 GtkTextBuffer *buffer;
10088 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10090 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10091 mark = gtk_text_buffer_get_insert(buffer);
10092 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10093 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10094 if (gtk_text_iter_backward_word_starts(&ins, 1))
10095 gtk_text_buffer_place_cursor(buffer, &ins);
10098 static void textview_move_end_of_line (GtkTextView *text)
10100 GtkTextBuffer *buffer;
10104 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10106 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10107 mark = gtk_text_buffer_get_insert(buffer);
10108 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10109 if (gtk_text_iter_forward_to_line_end(&ins))
10110 gtk_text_buffer_place_cursor(buffer, &ins);
10113 static void textview_move_next_line (GtkTextView *text)
10115 GtkTextBuffer *buffer;
10120 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10122 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10123 mark = gtk_text_buffer_get_insert(buffer);
10124 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10125 offset = gtk_text_iter_get_line_offset(&ins);
10126 if (gtk_text_iter_forward_line(&ins)) {
10127 gtk_text_iter_set_line_offset(&ins, offset);
10128 gtk_text_buffer_place_cursor(buffer, &ins);
10132 static void textview_move_previous_line (GtkTextView *text)
10134 GtkTextBuffer *buffer;
10139 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10141 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10142 mark = gtk_text_buffer_get_insert(buffer);
10143 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10144 offset = gtk_text_iter_get_line_offset(&ins);
10145 if (gtk_text_iter_backward_line(&ins)) {
10146 gtk_text_iter_set_line_offset(&ins, offset);
10147 gtk_text_buffer_place_cursor(buffer, &ins);
10151 static void textview_delete_forward_character (GtkTextView *text)
10153 GtkTextBuffer *buffer;
10155 GtkTextIter ins, end_iter;
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);
10163 if (gtk_text_iter_forward_char(&end_iter)) {
10164 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10168 static void textview_delete_backward_character (GtkTextView *text)
10170 GtkTextBuffer *buffer;
10172 GtkTextIter ins, end_iter;
10174 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10176 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10177 mark = gtk_text_buffer_get_insert(buffer);
10178 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10180 if (gtk_text_iter_backward_char(&end_iter)) {
10181 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10185 static void textview_delete_forward_word (GtkTextView *text)
10187 GtkTextBuffer *buffer;
10189 GtkTextIter ins, end_iter;
10191 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10193 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10194 mark = gtk_text_buffer_get_insert(buffer);
10195 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10197 if (gtk_text_iter_forward_word_end(&end_iter)) {
10198 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10202 static void textview_delete_backward_word (GtkTextView *text)
10204 GtkTextBuffer *buffer;
10206 GtkTextIter ins, end_iter;
10208 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10210 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10211 mark = gtk_text_buffer_get_insert(buffer);
10212 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10214 if (gtk_text_iter_backward_word_start(&end_iter)) {
10215 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10219 static void textview_delete_line (GtkTextView *text)
10221 GtkTextBuffer *buffer;
10223 GtkTextIter ins, start_iter, end_iter;
10225 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10227 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10228 mark = gtk_text_buffer_get_insert(buffer);
10229 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10232 gtk_text_iter_set_line_offset(&start_iter, 0);
10235 if (gtk_text_iter_ends_line(&end_iter)){
10236 if (!gtk_text_iter_forward_char(&end_iter))
10237 gtk_text_iter_backward_char(&start_iter);
10240 gtk_text_iter_forward_to_line_end(&end_iter);
10241 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10244 static void textview_delete_to_line_end (GtkTextView *text)
10246 GtkTextBuffer *buffer;
10248 GtkTextIter ins, end_iter;
10250 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10252 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10253 mark = gtk_text_buffer_get_insert(buffer);
10254 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10256 if (gtk_text_iter_ends_line(&end_iter))
10257 gtk_text_iter_forward_char(&end_iter);
10259 gtk_text_iter_forward_to_line_end(&end_iter);
10260 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10263 #define DO_ACTION(name, act) { \
10264 if(!strcmp(name, a_name)) { \
10268 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10270 const gchar *a_name = gtk_action_get_name(action);
10271 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10272 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10273 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10274 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10275 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10276 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10277 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10278 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10279 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10280 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10281 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10282 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10283 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10284 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10288 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10290 Compose *compose = (Compose *)data;
10291 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10292 ComposeCallAdvancedAction action = -1;
10294 action = compose_call_advanced_action_from_path(gaction);
10297 void (*do_action) (GtkTextView *text);
10298 } action_table[] = {
10299 {textview_move_beginning_of_line},
10300 {textview_move_forward_character},
10301 {textview_move_backward_character},
10302 {textview_move_forward_word},
10303 {textview_move_backward_word},
10304 {textview_move_end_of_line},
10305 {textview_move_next_line},
10306 {textview_move_previous_line},
10307 {textview_delete_forward_character},
10308 {textview_delete_backward_character},
10309 {textview_delete_forward_word},
10310 {textview_delete_backward_word},
10311 {textview_delete_line},
10312 {textview_delete_to_line_end}
10315 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10317 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10318 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10319 if (action_table[action].do_action)
10320 action_table[action].do_action(text);
10322 g_warning("Not implemented yet.");
10326 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10330 if (GTK_IS_EDITABLE(widget)) {
10331 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10332 gtk_editable_set_position(GTK_EDITABLE(widget),
10335 if (widget->parent && widget->parent->parent
10336 && widget->parent->parent->parent) {
10337 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10338 gint y = widget->allocation.y;
10339 gint height = widget->allocation.height;
10340 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10341 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10343 if (y < (int)shown->value) {
10344 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10346 if (y + height > (int)shown->value + (int)shown->page_size) {
10347 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10348 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10349 y + height - (int)shown->page_size - 1);
10351 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10352 (int)shown->upper - (int)shown->page_size - 1);
10359 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10360 compose->focused_editable = widget;
10362 #ifdef GENERIC_UMPC
10363 if (GTK_IS_TEXT_VIEW(widget)
10364 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10365 g_object_ref(compose->notebook);
10366 g_object_ref(compose->edit_vbox);
10367 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10368 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10369 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10370 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10371 g_object_unref(compose->notebook);
10372 g_object_unref(compose->edit_vbox);
10373 g_signal_handlers_block_by_func(G_OBJECT(widget),
10374 G_CALLBACK(compose_grab_focus_cb),
10376 gtk_widget_grab_focus(widget);
10377 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10378 G_CALLBACK(compose_grab_focus_cb),
10380 } else if (!GTK_IS_TEXT_VIEW(widget)
10381 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10382 g_object_ref(compose->notebook);
10383 g_object_ref(compose->edit_vbox);
10384 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10385 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10386 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10387 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10388 g_object_unref(compose->notebook);
10389 g_object_unref(compose->edit_vbox);
10390 g_signal_handlers_block_by_func(G_OBJECT(widget),
10391 G_CALLBACK(compose_grab_focus_cb),
10393 gtk_widget_grab_focus(widget);
10394 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10395 G_CALLBACK(compose_grab_focus_cb),
10401 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10403 compose->modified = TRUE;
10404 // compose_beautify_paragraph(compose, NULL, TRUE);
10405 #ifndef GENERIC_UMPC
10406 compose_set_title(compose);
10410 static void compose_wrap_cb(GtkAction *action, gpointer data)
10412 Compose *compose = (Compose *)data;
10413 compose_beautify_paragraph(compose, NULL, TRUE);
10416 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10418 Compose *compose = (Compose *)data;
10419 compose_wrap_all_full(compose, TRUE);
10422 static void compose_find_cb(GtkAction *action, gpointer data)
10424 Compose *compose = (Compose *)data;
10426 message_search_compose(compose);
10429 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10432 Compose *compose = (Compose *)data;
10433 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10434 if (compose->autowrap)
10435 compose_wrap_all_full(compose, TRUE);
10436 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10439 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10442 Compose *compose = (Compose *)data;
10443 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10446 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10448 Compose *compose = (Compose *)data;
10450 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10453 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10455 Compose *compose = (Compose *)data;
10457 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10460 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10462 g_free(compose->privacy_system);
10464 compose->privacy_system = g_strdup(account->default_privacy_system);
10465 compose_update_privacy_system_menu_item(compose, warn);
10468 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10470 Compose *compose = (Compose *)data;
10472 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10473 gtk_widget_show(compose->ruler_hbox);
10474 prefs_common.show_ruler = TRUE;
10476 gtk_widget_hide(compose->ruler_hbox);
10477 gtk_widget_queue_resize(compose->edit_vbox);
10478 prefs_common.show_ruler = FALSE;
10482 static void compose_attach_drag_received_cb (GtkWidget *widget,
10483 GdkDragContext *context,
10486 GtkSelectionData *data,
10489 gpointer user_data)
10491 Compose *compose = (Compose *)user_data;
10494 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10496 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10498 ) && gtk_drag_get_source_widget(context) !=
10499 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10500 list = uri_list_extract_filenames((const gchar *)data->data);
10501 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10502 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10503 compose_attach_append
10504 (compose, (const gchar *)tmp->data,
10505 utf8_filename, NULL);
10506 g_free(utf8_filename);
10508 if (list) compose_changed_cb(NULL, compose);
10509 list_free_strings(list);
10511 } else if (gtk_drag_get_source_widget(context)
10512 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10513 /* comes from our summaryview */
10514 SummaryView * summaryview = NULL;
10515 GSList * list = NULL, *cur = NULL;
10517 if (mainwindow_get_mainwindow())
10518 summaryview = mainwindow_get_mainwindow()->summaryview;
10521 list = summary_get_selected_msg_list(summaryview);
10523 for (cur = list; cur; cur = cur->next) {
10524 MsgInfo *msginfo = (MsgInfo *)cur->data;
10525 gchar *file = NULL;
10527 file = procmsg_get_message_file_full(msginfo,
10530 compose_attach_append(compose, (const gchar *)file,
10531 (const gchar *)file, "message/rfc822");
10535 g_slist_free(list);
10539 static gboolean compose_drag_drop(GtkWidget *widget,
10540 GdkDragContext *drag_context,
10542 guint time, gpointer user_data)
10544 /* not handling this signal makes compose_insert_drag_received_cb
10549 static void compose_insert_drag_received_cb (GtkWidget *widget,
10550 GdkDragContext *drag_context,
10553 GtkSelectionData *data,
10556 gpointer user_data)
10558 Compose *compose = (Compose *)user_data;
10561 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10564 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10566 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10568 AlertValue val = G_ALERTDEFAULT;
10570 list = uri_list_extract_filenames((const gchar *)data->data);
10571 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10572 /* Assume a list of no files, and data has ://, is a remote link */
10573 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10574 gchar *tmpfile = get_tmp_file();
10575 str_write_to_file(tmpdata, tmpfile);
10577 compose_insert_file(compose, tmpfile);
10578 claws_unlink(tmpfile);
10580 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10581 compose_beautify_paragraph(compose, NULL, TRUE);
10584 switch (prefs_common.compose_dnd_mode) {
10585 case COMPOSE_DND_ASK:
10586 val = alertpanel_full(_("Insert or attach?"),
10587 _("Do you want to insert the contents of the file(s) "
10588 "into the message body, or attach it to the email?"),
10589 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10590 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10592 case COMPOSE_DND_INSERT:
10593 val = G_ALERTALTERNATE;
10595 case COMPOSE_DND_ATTACH:
10596 val = G_ALERTOTHER;
10599 /* unexpected case */
10600 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10603 if (val & G_ALERTDISABLE) {
10604 val &= ~G_ALERTDISABLE;
10605 /* remember what action to perform by default, only if we don't click Cancel */
10606 if (val == G_ALERTALTERNATE)
10607 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10608 else if (val == G_ALERTOTHER)
10609 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10612 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10613 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10614 list_free_strings(list);
10617 } else if (val == G_ALERTOTHER) {
10618 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10619 list_free_strings(list);
10624 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10625 compose_insert_file(compose, (const gchar *)tmp->data);
10627 list_free_strings(list);
10629 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10632 #if GTK_CHECK_VERSION(2, 8, 0)
10633 /* do nothing, handled by GTK */
10635 gchar *tmpfile = get_tmp_file();
10636 str_write_to_file((const gchar *)data->data, tmpfile);
10637 compose_insert_file(compose, tmpfile);
10638 claws_unlink(tmpfile);
10640 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10644 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10647 static void compose_header_drag_received_cb (GtkWidget *widget,
10648 GdkDragContext *drag_context,
10651 GtkSelectionData *data,
10654 gpointer user_data)
10656 GtkEditable *entry = (GtkEditable *)user_data;
10657 gchar *email = (gchar *)data->data;
10659 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10662 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10663 gchar *decoded=g_new(gchar, strlen(email));
10666 email += strlen("mailto:");
10667 decode_uri(decoded, email); /* will fit */
10668 gtk_editable_delete_text(entry, 0, -1);
10669 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10670 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10674 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10677 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10679 Compose *compose = (Compose *)data;
10681 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10682 compose->return_receipt = TRUE;
10684 compose->return_receipt = FALSE;
10687 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10689 Compose *compose = (Compose *)data;
10691 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10692 compose->remove_references = TRUE;
10694 compose->remove_references = FALSE;
10697 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10698 ComposeHeaderEntry *headerentry)
10700 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10704 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10705 GdkEventKey *event,
10706 ComposeHeaderEntry *headerentry)
10708 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10709 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10710 !(event->state & GDK_MODIFIER_MASK) &&
10711 (event->keyval == GDK_BackSpace) &&
10712 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10713 gtk_container_remove
10714 (GTK_CONTAINER(headerentry->compose->header_table),
10715 headerentry->combo);
10716 gtk_container_remove
10717 (GTK_CONTAINER(headerentry->compose->header_table),
10718 headerentry->entry);
10719 headerentry->compose->header_list =
10720 g_slist_remove(headerentry->compose->header_list,
10722 g_free(headerentry);
10723 } else if (event->keyval == GDK_Tab) {
10724 if (headerentry->compose->header_last == headerentry) {
10725 /* Override default next focus, and give it to subject_entry
10726 * instead of notebook tabs
10728 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10729 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10736 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10737 ComposeHeaderEntry *headerentry)
10739 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10740 compose_create_header_entry(headerentry->compose);
10741 g_signal_handlers_disconnect_matched
10742 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10743 0, 0, NULL, NULL, headerentry);
10745 /* Automatically scroll down */
10746 GTK_EVENTS_FLUSH();
10747 compose_show_first_last_header(headerentry->compose, FALSE);
10753 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10755 GtkAdjustment *vadj;
10757 cm_return_if_fail(compose);
10758 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10759 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10760 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10761 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10762 gtk_adjustment_changed(vadj);
10765 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10766 const gchar *text, gint len, Compose *compose)
10768 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10769 (G_OBJECT(compose->text), "paste_as_quotation"));
10772 cm_return_if_fail(text != NULL);
10774 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10775 G_CALLBACK(text_inserted),
10777 if (paste_as_quotation) {
10779 const gchar *qmark;
10781 GtkTextIter start_iter;
10784 len = strlen(text);
10786 new_text = g_strndup(text, len);
10788 qmark = compose_quote_char_from_context(compose);
10790 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10791 gtk_text_buffer_place_cursor(buffer, iter);
10793 pos = gtk_text_iter_get_offset(iter);
10795 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10796 _("Quote format error at line %d."));
10797 quote_fmt_reset_vartable();
10799 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10800 GINT_TO_POINTER(paste_as_quotation - 1));
10802 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10803 gtk_text_buffer_place_cursor(buffer, iter);
10804 gtk_text_buffer_delete_mark(buffer, mark);
10806 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10807 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10808 compose_beautify_paragraph(compose, &start_iter, FALSE);
10809 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10810 gtk_text_buffer_delete_mark(buffer, mark);
10812 if (strcmp(text, "\n") || compose->automatic_break
10813 || gtk_text_iter_starts_line(iter)) {
10814 GtkTextIter before_ins;
10815 gtk_text_buffer_insert(buffer, iter, text, len);
10816 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10817 before_ins = *iter;
10818 gtk_text_iter_backward_chars(&before_ins, len);
10819 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10822 /* check if the preceding is just whitespace or quote */
10823 GtkTextIter start_line;
10824 gchar *tmp = NULL, *quote = NULL;
10825 gint quote_len = 0, is_normal = 0;
10826 start_line = *iter;
10827 gtk_text_iter_set_line_offset(&start_line, 0);
10828 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10831 if (*tmp == '\0') {
10834 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10842 gtk_text_buffer_insert(buffer, iter, text, len);
10844 gtk_text_buffer_insert_with_tags_by_name(buffer,
10845 iter, text, len, "no_join", NULL);
10850 if (!paste_as_quotation) {
10851 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10852 compose_beautify_paragraph(compose, iter, FALSE);
10853 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10854 gtk_text_buffer_delete_mark(buffer, mark);
10857 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10858 G_CALLBACK(text_inserted),
10860 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10862 if (prefs_common.autosave &&
10863 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10864 compose->draft_timeout_tag != -2 /* disabled while loading */)
10865 compose->draft_timeout_tag = g_timeout_add
10866 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10868 static gint compose_defer_auto_save_draft(Compose *compose)
10870 compose->draft_timeout_tag = -1;
10871 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10876 static void compose_check_all(GtkAction *action, gpointer data)
10878 Compose *compose = (Compose *)data;
10879 if (!compose->gtkaspell)
10882 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10883 claws_spell_entry_check_all(
10884 CLAWS_SPELL_ENTRY(compose->subject_entry));
10886 gtkaspell_check_all(compose->gtkaspell);
10889 static void compose_highlight_all(GtkAction *action, gpointer data)
10891 Compose *compose = (Compose *)data;
10892 if (compose->gtkaspell) {
10893 claws_spell_entry_recheck_all(
10894 CLAWS_SPELL_ENTRY(compose->subject_entry));
10895 gtkaspell_highlight_all(compose->gtkaspell);
10899 static void compose_check_backwards(GtkAction *action, gpointer data)
10901 Compose *compose = (Compose *)data;
10902 if (!compose->gtkaspell) {
10903 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10907 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10908 claws_spell_entry_check_backwards(
10909 CLAWS_SPELL_ENTRY(compose->subject_entry));
10911 gtkaspell_check_backwards(compose->gtkaspell);
10914 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10916 Compose *compose = (Compose *)data;
10917 if (!compose->gtkaspell) {
10918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10922 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10923 claws_spell_entry_check_forwards_go(
10924 CLAWS_SPELL_ENTRY(compose->subject_entry));
10926 gtkaspell_check_forwards_go(compose->gtkaspell);
10931 *\brief Guess originating forward account from MsgInfo and several
10932 * "common preference" settings. Return NULL if no guess.
10934 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10936 PrefsAccount *account = NULL;
10938 cm_return_val_if_fail(msginfo, NULL);
10939 cm_return_val_if_fail(msginfo->folder, NULL);
10940 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10942 if (msginfo->folder->prefs->enable_default_account)
10943 account = account_find_from_id(msginfo->folder->prefs->default_account);
10946 account = msginfo->folder->folder->account;
10948 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10950 Xstrdup_a(to, msginfo->to, return NULL);
10951 extract_address(to);
10952 account = account_find_from_address(to, FALSE);
10955 if (!account && prefs_common.forward_account_autosel) {
10956 gchar cc[BUFFSIZE];
10957 if (!procheader_get_header_from_msginfo
10958 (msginfo, cc,sizeof cc , "Cc:")) {
10959 gchar *buf = cc + strlen("Cc:");
10960 extract_address(buf);
10961 account = account_find_from_address(buf, FALSE);
10965 if (!account && prefs_common.forward_account_autosel) {
10966 gchar deliveredto[BUFFSIZE];
10967 if (!procheader_get_header_from_msginfo
10968 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10969 gchar *buf = deliveredto + strlen("Delivered-To:");
10970 extract_address(buf);
10971 account = account_find_from_address(buf, FALSE);
10978 gboolean compose_close(Compose *compose)
10982 if (!g_mutex_trylock(compose->mutex)) {
10983 /* we have to wait for the (possibly deferred by auto-save)
10984 * drafting to be done, before destroying the compose under
10986 debug_print("waiting for drafting to finish...\n");
10987 compose_allow_user_actions(compose, FALSE);
10988 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10991 cm_return_val_if_fail(compose, FALSE);
10992 gtkut_widget_get_uposition(compose->window, &x, &y);
10993 if (!compose->batch) {
10994 prefs_common.compose_x = x;
10995 prefs_common.compose_y = y;
10997 g_mutex_unlock(compose->mutex);
10998 compose_destroy(compose);
11003 * Add entry field for each address in list.
11004 * \param compose E-Mail composition object.
11005 * \param listAddress List of (formatted) E-Mail addresses.
11007 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11010 node = listAddress;
11012 addr = ( gchar * ) node->data;
11013 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11014 node = g_list_next( node );
11018 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11019 guint action, gboolean opening_multiple)
11021 gchar *body = NULL;
11022 GSList *new_msglist = NULL;
11023 MsgInfo *tmp_msginfo = NULL;
11024 gboolean originally_enc = FALSE;
11025 gboolean originally_sig = FALSE;
11026 Compose *compose = NULL;
11027 gchar *s_system = NULL;
11029 cm_return_if_fail(msgview != NULL);
11031 cm_return_if_fail(msginfo_list != NULL);
11033 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11034 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11035 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11037 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11038 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11039 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11040 orig_msginfo, mimeinfo);
11041 if (tmp_msginfo != NULL) {
11042 new_msglist = g_slist_append(NULL, tmp_msginfo);
11044 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11045 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11046 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11048 tmp_msginfo->folder = orig_msginfo->folder;
11049 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11050 if (orig_msginfo->tags) {
11051 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11052 tmp_msginfo->folder->tags_dirty = TRUE;
11058 if (!opening_multiple)
11059 body = messageview_get_selection(msgview);
11062 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11063 procmsg_msginfo_free(tmp_msginfo);
11064 g_slist_free(new_msglist);
11066 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11068 if (compose && originally_enc) {
11069 compose_force_encryption(compose, compose->account, FALSE, s_system);
11072 if (compose && originally_sig && compose->account->default_sign_reply) {
11073 compose_force_signing(compose, compose->account, s_system);
11077 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11080 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11083 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11084 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11085 GSList *cur = msginfo_list;
11086 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11087 "messages. Opening the windows "
11088 "could take some time. Do you "
11089 "want to continue?"),
11090 g_slist_length(msginfo_list));
11091 if (g_slist_length(msginfo_list) > 9
11092 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11093 != G_ALERTALTERNATE) {
11098 /* We'll open multiple compose windows */
11099 /* let the WM place the next windows */
11100 compose_force_window_origin = FALSE;
11101 for (; cur; cur = cur->next) {
11103 tmplist.data = cur->data;
11104 tmplist.next = NULL;
11105 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11107 compose_force_window_origin = TRUE;
11109 /* forwarding multiple mails as attachments is done via a
11110 * single compose window */
11111 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11115 void compose_set_position(Compose *compose, gint pos)
11117 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11119 gtkut_text_view_set_position(text, pos);
11122 gboolean compose_search_string(Compose *compose,
11123 const gchar *str, gboolean case_sens)
11125 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11127 return gtkut_text_view_search_string(text, str, case_sens);
11130 gboolean compose_search_string_backward(Compose *compose,
11131 const gchar *str, gboolean case_sens)
11133 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11135 return gtkut_text_view_search_string_backward(text, str, case_sens);
11138 /* allocate a msginfo structure and populate its data from a compose data structure */
11139 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11141 MsgInfo *newmsginfo;
11143 gchar buf[BUFFSIZE];
11145 cm_return_val_if_fail( compose != NULL, NULL );
11147 newmsginfo = procmsg_msginfo_new();
11150 get_rfc822_date(buf, sizeof(buf));
11151 newmsginfo->date = g_strdup(buf);
11154 if (compose->from_name) {
11155 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11156 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11160 if (compose->subject_entry)
11161 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11163 /* to, cc, reply-to, newsgroups */
11164 for (list = compose->header_list; list; list = list->next) {
11165 gchar *header = gtk_editable_get_chars(
11167 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11168 gchar *entry = gtk_editable_get_chars(
11169 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11171 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11172 if ( newmsginfo->to == NULL ) {
11173 newmsginfo->to = g_strdup(entry);
11174 } else if (entry && *entry) {
11175 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11176 g_free(newmsginfo->to);
11177 newmsginfo->to = tmp;
11180 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11181 if ( newmsginfo->cc == NULL ) {
11182 newmsginfo->cc = g_strdup(entry);
11183 } else if (entry && *entry) {
11184 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11185 g_free(newmsginfo->cc);
11186 newmsginfo->cc = tmp;
11189 if ( strcasecmp(header,
11190 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11191 if ( newmsginfo->newsgroups == NULL ) {
11192 newmsginfo->newsgroups = g_strdup(entry);
11193 } else if (entry && *entry) {
11194 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11195 g_free(newmsginfo->newsgroups);
11196 newmsginfo->newsgroups = tmp;
11204 /* other data is unset */
11210 /* update compose's dictionaries from folder dict settings */
11211 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11212 FolderItem *folder_item)
11214 cm_return_if_fail(compose != NULL);
11216 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11217 FolderItemPrefs *prefs = folder_item->prefs;
11219 if (prefs->enable_default_dictionary)
11220 gtkaspell_change_dict(compose->gtkaspell,
11221 prefs->default_dictionary, FALSE);
11222 if (folder_item->prefs->enable_default_alt_dictionary)
11223 gtkaspell_change_alt_dict(compose->gtkaspell,
11224 prefs->default_alt_dictionary);
11225 if (prefs->enable_default_dictionary
11226 || prefs->enable_default_alt_dictionary)
11227 compose_spell_menu_changed(compose);