2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2016 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/>.
21 #include "claws-features.h"
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 #ifndef USE_ALT_ADDRBOOK
64 #include "addressbook.h"
66 #include "addressbook-dbus.h"
67 #include "addressadd.h"
69 #include "folderview.h"
72 #include "stock_pixmap.h"
73 #include "send_message.h"
76 #include "customheader.h"
77 #include "prefs_common.h"
78 #include "prefs_account.h"
82 #include "procheader.h"
84 #include "statusbar.h"
86 #include "quoted-printable.h"
90 #include "gtkshruler.h"
92 #include "alertpanel.h"
93 #include "manage_window.h"
95 #include "folder_item_prefs.h"
96 #include "addr_compl.h"
97 #include "quote_fmt.h"
99 #include "foldersel.h"
102 #include "message_search.h"
103 #include "combobox.h"
107 #include "autofaces.h"
108 #include "spell_entry.h"
121 #define N_ATTACH_COLS (N_COL_COLUMNS)
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER
,
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER
,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD
,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD
,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE
,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE
,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE
,
133 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER
,
134 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER
,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD
,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD
,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE
,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
139 } ComposeCallAdvancedAction
;
143 PRIORITY_HIGHEST
= 1,
152 COMPOSE_INSERT_SUCCESS
,
153 COMPOSE_INSERT_READ_ERROR
,
154 COMPOSE_INSERT_INVALID_CHARACTER
,
155 COMPOSE_INSERT_NO_FILE
156 } ComposeInsertResult
;
160 COMPOSE_WRITE_FOR_SEND
,
161 COMPOSE_WRITE_FOR_STORE
166 COMPOSE_QUOTE_FORCED
,
173 SUBJECT_FIELD_PRESENT
,
178 #define B64_LINE_SIZE 57
179 #define B64_BUFFSIZE 77
181 #define MAX_REFERENCES_LEN 999
183 #define COMPOSE_DRAFT_TIMEOUT_UNSET -1
184 #define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
186 static GList
*compose_list
= NULL
;
187 static GSList
*extra_headers
= NULL
;
189 static Compose
*compose_generic_new (PrefsAccount
*account
,
193 GList
*listAddress
);
195 static Compose
*compose_create (PrefsAccount
*account
,
200 static void compose_entry_mark_default_to (Compose
*compose
,
201 const gchar
*address
);
202 static Compose
*compose_followup_and_reply_to (MsgInfo
*msginfo
,
203 ComposeQuoteMode quote_mode
,
207 static Compose
*compose_forward_multiple (PrefsAccount
*account
,
208 GSList
*msginfo_list
);
209 static Compose
*compose_reply (MsgInfo
*msginfo
,
210 ComposeQuoteMode quote_mode
,
215 static Compose
*compose_reply_mode (ComposeMode mode
,
216 GSList
*msginfo_list
,
218 static void compose_template_apply_fields(Compose
*compose
, Template
*tmpl
);
219 static void compose_update_privacy_systems_menu(Compose
*compose
);
221 static GtkWidget
*compose_account_option_menu_create
223 static void compose_set_out_encoding (Compose
*compose
);
224 static void compose_set_template_menu (Compose
*compose
);
225 static void compose_destroy (Compose
*compose
);
227 static MailField
compose_entries_set (Compose
*compose
,
229 ComposeEntryType to_type
);
230 static gint
compose_parse_header (Compose
*compose
,
232 static gint
compose_parse_manual_headers (Compose
*compose
,
234 HeaderEntry
*entries
);
235 static gchar
*compose_parse_references (const gchar
*ref
,
238 static gchar
*compose_quote_fmt (Compose
*compose
,
244 gboolean need_unescape
,
245 const gchar
*err_msg
);
247 static void compose_reply_set_entry (Compose
*compose
,
253 followup_and_reply_to
);
254 static void compose_reedit_set_entry (Compose
*compose
,
257 static void compose_insert_sig (Compose
*compose
,
259 static ComposeInsertResult
compose_insert_file (Compose
*compose
,
262 static gboolean
compose_attach_append (Compose
*compose
,
265 const gchar
*content_type
,
266 const gchar
*charset
);
267 static void compose_attach_parts (Compose
*compose
,
270 static gboolean
compose_beautify_paragraph (Compose
*compose
,
271 GtkTextIter
*par_iter
,
273 static void compose_wrap_all (Compose
*compose
);
274 static void compose_wrap_all_full (Compose
*compose
,
277 static void compose_set_title (Compose
*compose
);
278 static void compose_select_account (Compose
*compose
,
279 PrefsAccount
*account
,
282 static PrefsAccount
*compose_current_mail_account(void);
283 /* static gint compose_send (Compose *compose); */
284 static gboolean compose_check_for_valid_recipient
286 static gboolean
compose_check_entries (Compose
*compose
,
287 gboolean check_everything
);
288 static gint
compose_write_to_file (Compose
*compose
,
291 gboolean attach_parts
);
292 static gint
compose_write_body_to_file (Compose
*compose
,
294 static gint
compose_remove_reedit_target (Compose
*compose
,
296 static void compose_remove_draft (Compose
*compose
);
297 static gint
compose_queue_sub (Compose
*compose
,
301 gboolean check_subject
,
302 gboolean remove_reedit_target
);
303 static int compose_add_attachments (Compose
*compose
,
305 static gchar
*compose_get_header (Compose
*compose
);
306 static gchar
*compose_get_manual_headers_info (Compose
*compose
);
308 static void compose_convert_header (Compose
*compose
,
313 gboolean addr_field
);
315 static void compose_attach_info_free (AttachInfo
*ainfo
);
316 static void compose_attach_remove_selected (GtkAction
*action
,
319 static void compose_template_apply (Compose
*compose
,
322 static void compose_attach_property (GtkAction
*action
,
324 static void compose_attach_property_create (gboolean
*cancelled
);
325 static void attach_property_ok (GtkWidget
*widget
,
326 gboolean
*cancelled
);
327 static void attach_property_cancel (GtkWidget
*widget
,
328 gboolean
*cancelled
);
329 static gint
attach_property_delete_event (GtkWidget
*widget
,
331 gboolean
*cancelled
);
332 static gboolean
attach_property_key_pressed (GtkWidget
*widget
,
334 gboolean
*cancelled
);
336 static void compose_exec_ext_editor (Compose
*compose
);
338 static gint
compose_exec_ext_editor_real (const gchar
*file
,
339 GdkNativeWindow socket_wid
);
340 static gboolean
compose_ext_editor_kill (Compose
*compose
);
341 static gboolean
compose_input_cb (GIOChannel
*source
,
342 GIOCondition condition
,
344 static void compose_set_ext_editor_sensitive (Compose
*compose
,
346 static gboolean
compose_get_ext_editor_cmd_valid();
347 static gboolean
compose_get_ext_editor_uses_socket();
348 static gboolean compose_ext_editor_plug_removed_cb
351 #endif /* G_OS_UNIX */
353 static void compose_undo_state_changed (UndoMain
*undostruct
,
358 static void compose_create_header_entry (Compose
*compose
);
359 static void compose_add_header_entry (Compose
*compose
, const gchar
*header
,
360 gchar
*text
, ComposePrefType pref_type
);
361 static void compose_remove_header_entries(Compose
*compose
);
363 static void compose_update_priority_menu_item(Compose
* compose
);
365 static void compose_spell_menu_changed (void *data
);
366 static void compose_dict_changed (void *data
);
368 static void compose_add_field_list ( Compose
*compose
,
369 GList
*listAddress
);
371 /* callback functions */
373 static void compose_notebook_size_alloc (GtkNotebook
*notebook
,
374 GtkAllocation
*allocation
,
376 static gboolean
compose_edit_size_alloc (GtkEditable
*widget
,
377 GtkAllocation
*allocation
,
378 GtkSHRuler
*shruler
);
379 static void account_activated (GtkComboBox
*optmenu
,
381 static void attach_selected (GtkTreeView
*tree_view
,
382 GtkTreePath
*tree_path
,
383 GtkTreeViewColumn
*column
,
385 static gboolean
attach_button_pressed (GtkWidget
*widget
,
386 GdkEventButton
*event
,
388 static gboolean
attach_key_pressed (GtkWidget
*widget
,
391 static void compose_send_cb (GtkAction
*action
, gpointer data
);
392 static void compose_send_later_cb (GtkAction
*action
, gpointer data
);
394 static void compose_save_cb (GtkAction
*action
,
397 static void compose_attach_cb (GtkAction
*action
,
399 static void compose_insert_file_cb (GtkAction
*action
,
401 static void compose_insert_sig_cb (GtkAction
*action
,
403 static void compose_replace_sig_cb (GtkAction
*action
,
406 static void compose_close_cb (GtkAction
*action
,
408 static void compose_print_cb (GtkAction
*action
,
411 static void compose_set_encoding_cb (GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
413 static void compose_address_cb (GtkAction
*action
,
415 static void about_show_cb (GtkAction
*action
,
417 static void compose_template_activate_cb(GtkWidget
*widget
,
420 static void compose_ext_editor_cb (GtkAction
*action
,
423 static gint
compose_delete_cb (GtkWidget
*widget
,
427 static void compose_undo_cb (GtkAction
*action
,
429 static void compose_redo_cb (GtkAction
*action
,
431 static void compose_cut_cb (GtkAction
*action
,
433 static void compose_copy_cb (GtkAction
*action
,
435 static void compose_paste_cb (GtkAction
*action
,
437 static void compose_paste_as_quote_cb (GtkAction
*action
,
439 static void compose_paste_no_wrap_cb (GtkAction
*action
,
441 static void compose_paste_wrap_cb (GtkAction
*action
,
443 static void compose_allsel_cb (GtkAction
*action
,
446 static void compose_advanced_action_cb (GtkAction
*action
,
449 static void compose_grab_focus_cb (GtkWidget
*widget
,
452 static void compose_changed_cb (GtkTextBuffer
*textbuf
,
455 static void compose_wrap_cb (GtkAction
*action
,
457 static void compose_wrap_all_cb (GtkAction
*action
,
459 static void compose_find_cb (GtkAction
*action
,
461 static void compose_toggle_autowrap_cb (GtkToggleAction
*action
,
463 static void compose_toggle_autoindent_cb(GtkToggleAction
*action
,
466 static void compose_toggle_ruler_cb (GtkToggleAction
*action
,
468 static void compose_toggle_sign_cb (GtkToggleAction
*action
,
470 static void compose_toggle_encrypt_cb (GtkToggleAction
*action
,
472 static void compose_set_privacy_system_cb(GtkWidget
*widget
, gpointer data
);
473 static void compose_update_privacy_system_menu_item(Compose
* compose
, gboolean warn
);
474 static void activate_privacy_system (Compose
*compose
,
475 PrefsAccount
*account
,
477 static void compose_use_signing(Compose
*compose
, gboolean use_signing
);
478 static void compose_use_encryption(Compose
*compose
, gboolean use_encryption
);
479 static void compose_toggle_return_receipt_cb(GtkToggleAction
*action
,
481 static void compose_toggle_remove_refs_cb(GtkToggleAction
*action
,
483 static void compose_set_priority_cb (GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
484 static void compose_reply_change_mode (Compose
*compose
, ComposeMode action
);
485 static void compose_reply_change_mode_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
);
487 static void compose_attach_drag_received_cb (GtkWidget
*widget
,
488 GdkDragContext
*drag_context
,
491 GtkSelectionData
*data
,
495 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
496 GdkDragContext
*drag_context
,
499 GtkSelectionData
*data
,
503 static void compose_header_drag_received_cb (GtkWidget
*widget
,
504 GdkDragContext
*drag_context
,
507 GtkSelectionData
*data
,
512 static gboolean
compose_drag_drop (GtkWidget
*widget
,
513 GdkDragContext
*drag_context
,
515 guint time
, gpointer user_data
);
516 static gboolean completion_set_focus_to_subject
521 static void text_inserted (GtkTextBuffer
*buffer
,
526 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
527 ComposeQuoteMode quote_mode
,
531 gboolean followup_and_reply_to
,
534 static void compose_headerentry_changed_cb (GtkWidget
*entry
,
535 ComposeHeaderEntry
*headerentry
);
536 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
538 ComposeHeaderEntry
*headerentry
);
539 static gboolean
compose_headerentry_button_clicked_cb (GtkWidget
*button
,
540 ComposeHeaderEntry
*headerentry
);
542 static void compose_show_first_last_header (Compose
*compose
, gboolean show_first
);
544 static void compose_allow_user_actions (Compose
*compose
, gboolean allow
);
546 static void compose_nothing_cb (GtkAction
*action
, gpointer data
)
552 static void compose_check_all (GtkAction
*action
, gpointer data
);
553 static void compose_highlight_all (GtkAction
*action
, gpointer data
);
554 static void compose_check_backwards (GtkAction
*action
, gpointer data
);
555 static void compose_check_forwards_go (GtkAction
*action
, gpointer data
);
558 static PrefsAccount
*compose_guess_forward_account_from_msginfo (MsgInfo
*msginfo
);
560 static MsgInfo
*compose_msginfo_new_from_compose(Compose
*compose
);
563 static void compose_set_dictionaries_from_folder_prefs(Compose
*compose
,
564 FolderItem
*folder_item
);
566 static void compose_attach_update_label(Compose
*compose
);
567 static void compose_set_folder_prefs(Compose
*compose
, FolderItem
*folder
,
568 gboolean respect_default_to
);
569 static void compose_subject_entry_activated(GtkWidget
*widget
, gpointer data
);
570 static void from_name_activate_cb(GtkWidget
*widget
, gpointer data
);
572 static GtkActionEntry compose_popup_entries
[] =
574 {"Compose", NULL
, "Compose" },
575 {"Compose/Add", NULL
, N_("_Add..."), NULL
, NULL
, G_CALLBACK(compose_attach_cb
) },
576 {"Compose/Remove", NULL
, N_("_Remove"), NULL
, NULL
, G_CALLBACK(compose_attach_remove_selected
) },
577 {"Compose/---", NULL
, "---", NULL
, NULL
, NULL
},
578 {"Compose/Properties", NULL
, N_("_Properties..."), NULL
, NULL
, G_CALLBACK(compose_attach_property
) },
581 static GtkActionEntry compose_entries
[] =
583 {"Menu", NULL
, "Menu" },
585 {"Message", NULL
, N_("_Message") },
586 {"Edit", NULL
, N_("_Edit") },
588 {"Spelling", NULL
, N_("_Spelling") },
590 {"Options", NULL
, N_("_Options") },
591 {"Tools", NULL
, N_("_Tools") },
592 {"Help", NULL
, N_("_Help") },
594 {"Message/Send", NULL
, N_("S_end"), "<control>Return", NULL
, G_CALLBACK(compose_send_cb
) },
595 {"Message/SendLater", NULL
, N_("Send _later"), "<shift><control>S", NULL
, G_CALLBACK(compose_send_later_cb
) },
596 {"Message/---", NULL
, "---" },
598 {"Message/AttachFile", NULL
, N_("_Attach file"), "<control>M", NULL
, G_CALLBACK(compose_attach_cb
) },
599 {"Message/InsertFile", NULL
, N_("_Insert file"), "<control>I", NULL
, G_CALLBACK(compose_insert_file_cb
) },
600 {"Message/InsertSig", NULL
, N_("Insert si_gnature"), "<control>G", NULL
, G_CALLBACK(compose_insert_sig_cb
) },
601 {"Message/ReplaceSig", NULL
, N_("_Replace signature"), NULL
, NULL
, G_CALLBACK(compose_replace_sig_cb
) },
602 /* {"Message/---", NULL, "---" }, */
603 {"Message/Save", NULL
, N_("_Save"), "<control>S", NULL
, G_CALLBACK(compose_save_cb
) }, /*COMPOSE_KEEP_EDITING*/
604 /* {"Message/---", NULL, "---" }, */
605 {"Message/Print", NULL
, N_("_Print"), NULL
, NULL
, G_CALLBACK(compose_print_cb
) },
606 /* {"Message/---", NULL, "---" }, */
607 {"Message/Close", NULL
, N_("_Close"), "<control>W", NULL
, G_CALLBACK(compose_close_cb
) },
610 {"Edit/Undo", NULL
, N_("_Undo"), "<control>Z", NULL
, G_CALLBACK(compose_undo_cb
) },
611 {"Edit/Redo", NULL
, N_("_Redo"), "<control>Y", NULL
, G_CALLBACK(compose_redo_cb
) },
612 {"Edit/---", NULL
, "---" },
614 {"Edit/Cut", NULL
, N_("Cu_t"), "<control>X", NULL
, G_CALLBACK(compose_cut_cb
) },
615 {"Edit/Copy", NULL
, N_("_Copy"), "<control>C", NULL
, G_CALLBACK(compose_copy_cb
) },
616 {"Edit/Paste", NULL
, N_("_Paste"), "<control>V", NULL
, G_CALLBACK(compose_paste_cb
) },
618 {"Edit/SpecialPaste", NULL
, N_("_Special paste") },
619 {"Edit/SpecialPaste/AsQuotation", NULL
, N_("As _quotation"), NULL
, NULL
, G_CALLBACK(compose_paste_as_quote_cb
) },
620 {"Edit/SpecialPaste/Wrapped", NULL
, N_("_Wrapped"), NULL
, NULL
, G_CALLBACK(compose_paste_wrap_cb
) },
621 {"Edit/SpecialPaste/Unwrapped", NULL
, N_("_Unwrapped"), NULL
, NULL
, G_CALLBACK(compose_paste_no_wrap_cb
) },
623 {"Edit/SelectAll", NULL
, N_("Select _all"), "<control>A", NULL
, G_CALLBACK(compose_allsel_cb
) },
625 {"Edit/Advanced", NULL
, N_("A_dvanced") },
626 {"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*/
627 {"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*/
628 {"Edit/Advanced/BackWord", NULL
, N_("Move a word backward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
629 {"Edit/Advanced/ForwWord", NULL
, N_("Move a word forward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
630 {"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*/
631 {"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*/
632 {"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*/
633 {"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*/
634 {"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*/
635 {"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*/
636 {"Edit/Advanced/DelBackWord", NULL
, N_("Delete a word backward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
637 {"Edit/Advanced/DelForwWord", NULL
, N_("Delete a word forward"), NULL
, NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
638 {"Edit/Advanced/DelLine", NULL
, N_("Delete line"), "<control>U", NULL
, G_CALLBACK(compose_advanced_action_cb
) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
639 {"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*/
641 /* {"Edit/---", NULL, "---" }, */
642 {"Edit/Find", NULL
, N_("_Find"), "<control>F", NULL
, G_CALLBACK(compose_find_cb
) },
644 /* {"Edit/---", NULL, "---" }, */
645 {"Edit/WrapPara", NULL
, N_("_Wrap current paragraph"), "<control>L", NULL
, G_CALLBACK(compose_wrap_cb
) }, /* 0 */
646 {"Edit/WrapAllLines", NULL
, N_("Wrap all long _lines"), "<control><alt>L", NULL
, G_CALLBACK(compose_wrap_all_cb
) }, /* 1 */
647 /* {"Edit/---", NULL, "---" }, */
648 {"Edit/ExtEditor", NULL
, N_("Edit with e_xternal editor"), "<shift><control>X", NULL
, G_CALLBACK(compose_ext_editor_cb
) },
651 {"Spelling/CheckAllSel", NULL
, N_("_Check all or check selection"), NULL
, NULL
, G_CALLBACK(compose_check_all
) },
652 {"Spelling/HighlightAll", NULL
, N_("_Highlight all misspelled words"), NULL
, NULL
, G_CALLBACK(compose_highlight_all
) },
653 {"Spelling/CheckBackwards", NULL
, N_("Check _backwards misspelled word"), NULL
, NULL
, G_CALLBACK(compose_check_backwards
) },
654 {"Spelling/ForwardNext", NULL
, N_("_Forward to next misspelled word"), NULL
, NULL
, G_CALLBACK(compose_check_forwards_go
) },
656 {"Spelling/---", NULL
, "---" },
657 {"Spelling/Options", NULL
, N_("_Options") },
662 {"Options/ReplyMode", NULL
, N_("Reply _mode") },
663 {"Options/---", NULL
, "---" },
664 {"Options/PrivacySystem", NULL
, N_("Privacy _System") },
665 {"Options/PrivacySystem/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
667 /* {"Options/---", NULL, "---" }, */
669 {"Options/Priority", NULL
, N_("_Priority") },
671 {"Options/Encoding", NULL
, N_("Character _encoding") },
672 {"Options/Encoding/---", NULL
, "---" },
673 #define ENC_ACTION(cs_char,c_char,string) \
674 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
676 {"Options/Encoding/Western", NULL
, N_("Western European") },
677 {"Options/Encoding/Baltic", NULL
, N_("Baltic") },
678 {"Options/Encoding/Hebrew", NULL
, N_("Hebrew") },
679 {"Options/Encoding/Arabic", NULL
, N_("Arabic") },
680 {"Options/Encoding/Cyrillic", NULL
, N_("Cyrillic") },
681 {"Options/Encoding/Japanese", NULL
, N_("Japanese") },
682 {"Options/Encoding/Chinese", NULL
, N_("Chinese") },
683 {"Options/Encoding/Korean", NULL
, N_("Korean") },
684 {"Options/Encoding/Thai", NULL
, N_("Thai") },
687 {"Tools/AddressBook", NULL
, N_("_Address book"), NULL
, NULL
, G_CALLBACK(compose_address_cb
) },
689 {"Tools/Template", NULL
, N_("_Template") },
690 {"Tools/Template/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
691 {"Tools/Actions", NULL
, N_("Actio_ns") },
692 {"Tools/Actions/PlaceHolder", NULL
, "Placeholder", NULL
, NULL
, G_CALLBACK(compose_nothing_cb
) },
695 {"Help/About", NULL
, N_("_About"), NULL
, NULL
, G_CALLBACK(about_show_cb
) },
698 static GtkToggleActionEntry compose_toggle_entries
[] =
700 {"Edit/AutoWrap", NULL
, N_("Aut_o wrapping"), "<shift><control>L", NULL
, G_CALLBACK(compose_toggle_autowrap_cb
) }, /* TOGGLE */
701 {"Edit/AutoIndent", NULL
, N_("Auto _indent"), NULL
, NULL
, G_CALLBACK(compose_toggle_autoindent_cb
) }, /* TOGGLE */
702 {"Options/Sign", NULL
, N_("Si_gn"), NULL
, NULL
, G_CALLBACK(compose_toggle_sign_cb
) }, /* Toggle */
703 {"Options/Encrypt", NULL
, N_("_Encrypt"), NULL
, NULL
, G_CALLBACK(compose_toggle_encrypt_cb
) }, /* Toggle */
704 {"Options/RequestRetRcpt", NULL
, N_("_Request Return Receipt"), NULL
, NULL
, G_CALLBACK(compose_toggle_return_receipt_cb
) }, /* TOGGLE */
705 {"Options/RemoveReferences", NULL
, N_("Remo_ve references"), NULL
, NULL
, G_CALLBACK(compose_toggle_remove_refs_cb
) }, /* TOGGLE */
706 {"Tools/ShowRuler", NULL
, N_("Show _ruler"), NULL
, NULL
, G_CALLBACK(compose_toggle_ruler_cb
) }, /* Toggle */
709 static GtkRadioActionEntry compose_radio_rm_entries
[] =
711 {"Options/ReplyMode/Normal", NULL
, N_("_Normal"), NULL
, NULL
, COMPOSE_REPLY
}, /* RADIO compose_reply_change_mode_cb */
712 {"Options/ReplyMode/All", NULL
, N_("_All"), NULL
, NULL
, COMPOSE_REPLY_TO_ALL
}, /* RADIO compose_reply_change_mode_cb */
713 {"Options/ReplyMode/Sender", NULL
, N_("_Sender"), NULL
, NULL
, COMPOSE_REPLY_TO_SENDER
}, /* RADIO compose_reply_change_mode_cb */
714 {"Options/ReplyMode/List", NULL
, N_("_Mailing-list"), NULL
, NULL
, COMPOSE_REPLY_TO_LIST
}, /* RADIO compose_reply_change_mode_cb */
717 static GtkRadioActionEntry compose_radio_prio_entries
[] =
719 {"Options/Priority/Highest", NULL
, N_("_Highest"), NULL
, NULL
, PRIORITY_HIGHEST
}, /* RADIO compose_set_priority_cb */
720 {"Options/Priority/High", NULL
, N_("Hi_gh"), NULL
, NULL
, PRIORITY_HIGH
}, /* RADIO compose_set_priority_cb */
721 {"Options/Priority/Normal", NULL
, N_("_Normal"), NULL
, NULL
, PRIORITY_NORMAL
}, /* RADIO compose_set_priority_cb */
722 {"Options/Priority/Low", NULL
, N_("Lo_w"), NULL
, NULL
, PRIORITY_LOW
}, /* RADIO compose_set_priority_cb */
723 {"Options/Priority/Lowest", NULL
, N_("_Lowest"), NULL
, NULL
, PRIORITY_LOWEST
}, /* RADIO compose_set_priority_cb */
726 static GtkRadioActionEntry compose_radio_enc_entries
[] =
728 ENC_ACTION(CS_AUTO
, C_AUTO
, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION(CS_US_ASCII
, C_US_ASCII
, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_UTF_8
, C_UTF_8
, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Western/"CS_ISO_8859_1
, C_ISO_8859_1
, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Western/"CS_ISO_8859_15
, C_ISO_8859_15
, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Western/"CS_WINDOWS_1252
, C_WINDOWS_1252
, "Windows-1252"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION(CS_ISO_8859_2
, C_ISO_8859_2
, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Baltic/"CS_ISO_8859_13
, C_ISO_8859_13
, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Baltic/"CS_ISO_8859_4
, C_ISO_8859_14
, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION(CS_ISO_8859_7
, C_ISO_8859_7
, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Hebrew/"CS_ISO_8859_8
, C_ISO_8859_8
, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Hebrew/"CS_WINDOWS_1255
, C_WINDOWS_1255
, "Windows-1255"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Arabic/"CS_ISO_8859_6
, C_ISO_8859_6
, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Arabic/"CS_WINDOWS_1256
, C_WINDOWS_1256
, "Windows-1256"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION(CS_ISO_8859_9
, C_ISO_8859_9
, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Cyrillic/"CS_ISO_8859_5
, C_ISO_8859_5
, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Cyrillic/"CS_KOI8_R
, C_KOI8_R
, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Cyrillic/"CS_MACCYR
, C_MACCYR
, "_Mac-Cyrillic"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Cyrillic/"CS_KOI8_U
, C_KOI8_U
, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251
, C_WINDOWS_1251
, "Windows-1251"), /* RADIO compose_set_encoding_cb */
748 ENC_ACTION("Japanese/"CS_ISO_2022_JP
, C_ISO_2022_JP
, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
749 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2
, C_ISO_2022_JP_2
, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
750 ENC_ACTION("Japanese/"CS_EUC_JP
, C_EUC_JP
, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
751 ENC_ACTION("Japanese/"CS_SHIFT_JIS
, C_SHIFT_JIS
, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
752 ENC_ACTION("Chinese/"CS_GB18030
, C_GB18030
, "_GB18030"), /* RADIO compose_set_encoding_cb */
753 ENC_ACTION("Chinese/"CS_GB2312
, C_GB2312
, "_GB2312"), /* RADIO compose_set_encoding_cb */
754 ENC_ACTION("Chinese/"CS_GBK
, C_GBK
, "GB_K"), /* RADIO compose_set_encoding_cb */
755 ENC_ACTION("Chinese/"CS_BIG5
, C_BIG5
, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
756 ENC_ACTION("Chinese/"CS_EUC_TW
, C_EUC_TW
, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
757 ENC_ACTION("Korean/"CS_EUC_KR
, C_EUC_KR
, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
758 ENC_ACTION("Korean/"CS_ISO_2022_KR
, C_ISO_2022_KR
, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
759 ENC_ACTION("Thai/"CS_TIS_620
, C_TIS_620
, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
760 ENC_ACTION("Thai/"CS_WINDOWS_874
, C_WINDOWS_874
, "_Windows-874"), /* RADIO compose_set_encoding_cb */
763 static GtkTargetEntry compose_mime_types
[] =
765 {"text/uri-list", 0, 0},
766 {"UTF8_STRING", 0, 0},
770 static gboolean
compose_put_existing_to_front(MsgInfo
*info
)
772 const GList
*compose_list
= compose_get_compose_list();
773 const GList
*elem
= NULL
;
776 for (elem
= compose_list
; elem
!= NULL
&& elem
->data
!= NULL
;
778 Compose
*c
= (Compose
*)elem
->data
;
780 if (!c
->targetinfo
|| !c
->targetinfo
->msgid
||
784 if (!strcmp(c
->targetinfo
->msgid
, info
->msgid
)) {
785 gtkut_window_popup(c
->window
);
793 static GdkColor quote_color1
=
794 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
795 static GdkColor quote_color2
=
796 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
797 static GdkColor quote_color3
=
798 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
800 static GdkColor quote_bgcolor1
=
801 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
802 static GdkColor quote_bgcolor2
=
803 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
804 static GdkColor quote_bgcolor3
=
805 {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
807 static GdkColor signature_color
= {
814 static GdkColor uri_color
= {
821 static void compose_create_tags(GtkTextView
*text
, Compose
*compose
)
823 GtkTextBuffer
*buffer
;
824 GdkColor black
= {(gulong
)0, (gushort
)0, (gushort
)0, (gushort
)0};
825 #if !GTK_CHECK_VERSION(2, 24, 0)
832 buffer
= gtk_text_view_get_buffer(text
);
834 if (prefs_common
.enable_color
) {
835 /* grab the quote colors, converting from an int to a GdkColor */
836 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level1_col
,
838 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level2_col
,
840 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level3_col
,
842 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level1_bgcol
,
844 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level2_bgcol
,
846 gtkut_convert_int_to_gdk_color(prefs_common
.quote_level3_bgcol
,
848 gtkut_convert_int_to_gdk_color(prefs_common
.signature_col
,
850 gtkut_convert_int_to_gdk_color(prefs_common
.uri_col
,
853 signature_color
= quote_color1
= quote_color2
= quote_color3
=
854 quote_bgcolor1
= quote_bgcolor2
= quote_bgcolor3
= uri_color
= black
;
857 if (prefs_common
.enable_color
&& prefs_common
.enable_bgcolor
) {
858 compose
->quote0_tag
= gtk_text_buffer_create_tag(buffer
, "quote0",
859 "foreground-gdk", "e_color1
,
860 "paragraph-background-gdk", "e_bgcolor1
,
862 compose
->quote1_tag
= gtk_text_buffer_create_tag(buffer
, "quote1",
863 "foreground-gdk", "e_color2
,
864 "paragraph-background-gdk", "e_bgcolor2
,
866 compose
->quote2_tag
= gtk_text_buffer_create_tag(buffer
, "quote2",
867 "foreground-gdk", "e_color3
,
868 "paragraph-background-gdk", "e_bgcolor3
,
871 compose
->quote0_tag
= gtk_text_buffer_create_tag(buffer
, "quote0",
872 "foreground-gdk", "e_color1
,
874 compose
->quote1_tag
= gtk_text_buffer_create_tag(buffer
, "quote1",
875 "foreground-gdk", "e_color2
,
877 compose
->quote2_tag
= gtk_text_buffer_create_tag(buffer
, "quote2",
878 "foreground-gdk", "e_color3
,
882 compose
->signature_tag
= gtk_text_buffer_create_tag(buffer
, "signature",
883 "foreground-gdk", &signature_color
,
886 compose
->uri_tag
= gtk_text_buffer_create_tag(buffer
, "link",
887 "foreground-gdk", &uri_color
,
889 compose
->no_wrap_tag
= gtk_text_buffer_create_tag(buffer
, "no_wrap", NULL
);
890 compose
->no_join_tag
= gtk_text_buffer_create_tag(buffer
, "no_join", NULL
);
892 #if !GTK_CHECK_VERSION(2, 24, 0)
893 color
[0] = quote_color1
;
894 color
[1] = quote_color2
;
895 color
[2] = quote_color3
;
896 color
[3] = quote_bgcolor1
;
897 color
[4] = quote_bgcolor2
;
898 color
[5] = quote_bgcolor3
;
899 color
[6] = signature_color
;
900 color
[7] = uri_color
;
902 cmap
= gdk_drawable_get_colormap(gtk_widget_get_window(compose
->window
));
903 gdk_colormap_alloc_colors(cmap
, color
, 8, FALSE
, TRUE
, success
);
905 for (i
= 0; i
< 8; i
++) {
906 if (success
[i
] == FALSE
) {
907 g_warning("Compose: color allocation failed.");
908 quote_color1
= quote_color2
= quote_color3
=
909 quote_bgcolor1
= quote_bgcolor2
= quote_bgcolor3
=
910 signature_color
= uri_color
= black
;
916 Compose
*compose_new(PrefsAccount
*account
, const gchar
*mailto
,
919 return compose_generic_new(account
, mailto
, NULL
, attach_files
, NULL
);
922 Compose
*compose_new_with_folderitem(PrefsAccount
*account
, FolderItem
*item
, const gchar
*mailto
)
924 return compose_generic_new(account
, mailto
, item
, NULL
, NULL
);
927 Compose
*compose_new_with_list( PrefsAccount
*account
, GList
*listAddress
)
929 return compose_generic_new( account
, NULL
, NULL
, NULL
, listAddress
);
932 #define SCROLL_TO_CURSOR(compose) { \
933 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
934 gtk_text_view_get_buffer( \
935 GTK_TEXT_VIEW(compose->text))); \
936 gtk_text_view_scroll_mark_onscreen( \
937 GTK_TEXT_VIEW(compose->text), \
941 static void compose_set_save_to(Compose
*compose
, const gchar
*folderidentifier
)
944 if (folderidentifier
) {
945 #if !GTK_CHECK_VERSION(2, 24, 0)
946 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose
->savemsg_combo
));
948 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
));
950 prefs_common
.compose_save_to_history
= add_history(
951 prefs_common
.compose_save_to_history
, folderidentifier
);
952 #if !GTK_CHECK_VERSION(2, 24, 0)
953 combobox_set_popdown_strings(GTK_COMBO_BOX(compose
->savemsg_combo
),
954 prefs_common
.compose_save_to_history
);
956 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
),
957 prefs_common
.compose_save_to_history
);
961 entry
= GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose
->savemsg_combo
)));
962 if (folderidentifier
)
963 gtk_entry_set_text(GTK_ENTRY(entry
), folderidentifier
);
965 gtk_entry_set_text(GTK_ENTRY(entry
), "");
968 static gchar
*compose_get_save_to(Compose
*compose
)
971 gchar
*result
= NULL
;
972 entry
= GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose
->savemsg_combo
)));
973 result
= gtk_editable_get_chars(entry
, 0, -1);
976 #if !GTK_CHECK_VERSION(2, 24, 0)
977 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose
->savemsg_combo
));
979 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
));
981 prefs_common
.compose_save_to_history
= add_history(
982 prefs_common
.compose_save_to_history
, result
);
983 #if !GTK_CHECK_VERSION(2, 24, 0)
984 combobox_set_popdown_strings(GTK_COMBO_BOX(compose
->savemsg_combo
),
985 prefs_common
.compose_save_to_history
);
987 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(compose
->savemsg_combo
),
988 prefs_common
.compose_save_to_history
);
994 Compose
*compose_generic_new(PrefsAccount
*account
, const gchar
*mailto
, FolderItem
*item
,
995 GList
*attach_files
, GList
*listAddress
)
998 GtkTextView
*textview
;
999 GtkTextBuffer
*textbuf
;
1001 const gchar
*subject_format
= NULL
;
1002 const gchar
*body_format
= NULL
;
1003 gchar
*mailto_from
= NULL
;
1004 PrefsAccount
*mailto_account
= NULL
;
1005 MsgInfo
* dummyinfo
= NULL
;
1006 gint cursor_pos
= -1;
1007 MailField mfield
= NO_FIELD_PRESENT
;
1011 /* check if mailto defines a from */
1012 if (mailto
&& *mailto
!= '\0') {
1013 scan_mailto_url(mailto
, &mailto_from
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
1014 /* mailto defines a from, check if we can get account prefs from it,
1015 if not, the account prefs will be guessed using other ways, but we'll keep
1018 mailto_account
= account_find_from_address(mailto_from
, TRUE
);
1019 if (mailto_account
== NULL
) {
1021 Xstrdup_a(tmp_from
, mailto_from
, return NULL
);
1022 extract_address(tmp_from
);
1023 mailto_account
= account_find_from_address(tmp_from
, TRUE
);
1027 account
= mailto_account
;
1030 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1031 if (!mailto_account
&& item
&& item
->prefs
&& item
->prefs
->enable_default_account
)
1032 account
= account_find_from_id(item
->prefs
->default_account
);
1034 /* if no account prefs set, fallback to the current one */
1035 if (!account
) account
= cur_account
;
1036 cm_return_val_if_fail(account
!= NULL
, NULL
);
1038 compose
= compose_create(account
, item
, COMPOSE_NEW
, FALSE
);
1040 /* override from name if mailto asked for it */
1042 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), mailto_from
);
1043 g_free(mailto_from
);
1045 /* override from name according to folder properties */
1046 if (item
&& item
->prefs
&&
1047 item
->prefs
->compose_with_format
&&
1048 item
->prefs
->compose_override_from_format
&&
1049 *item
->prefs
->compose_override_from_format
!= '\0') {
1054 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1056 /* decode \-escape sequences in the internal representation of the quote format */
1057 tmp
= g_malloc(strlen(item
->prefs
->compose_override_from_format
)+1);
1058 pref_get_unescaped_pref(tmp
, item
->prefs
->compose_override_from_format
);
1061 quote_fmt_init(dummyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1062 compose
->gtkaspell
);
1064 quote_fmt_init(dummyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1066 quote_fmt_scan_string(tmp
);
1069 buf
= quote_fmt_get_buffer();
1071 alertpanel_error(_("New message From format error."));
1073 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1074 quote_fmt_reset_vartable();
1079 compose
->replyinfo
= NULL
;
1080 compose
->fwdinfo
= NULL
;
1082 textview
= GTK_TEXT_VIEW(compose
->text
);
1083 textbuf
= gtk_text_view_get_buffer(textview
);
1084 compose_create_tags(textview
, compose
);
1086 undo_block(compose
->undostruct
);
1088 compose_set_dictionaries_from_folder_prefs(compose
, item
);
1091 if (account
->auto_sig
)
1092 compose_insert_sig(compose
, FALSE
);
1093 gtk_text_buffer_get_start_iter(textbuf
, &iter
);
1094 gtk_text_buffer_place_cursor(textbuf
, &iter
);
1096 if (account
->protocol
!= A_NNTP
) {
1097 if (mailto
&& *mailto
!= '\0') {
1098 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_TO
);
1101 compose_set_folder_prefs(compose
, item
, TRUE
);
1103 if (item
&& item
->ret_rcpt
) {
1104 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
1107 if (mailto
&& *mailto
!= '\0') {
1108 if (!strchr(mailto
, '@'))
1109 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_NEWSGROUPS
);
1111 mfield
= compose_entries_set(compose
, mailto
, COMPOSE_TO
);
1112 } else if (item
&& FOLDER_CLASS(item
->folder
) == news_get_class()) {
1113 compose_entry_append(compose
, item
->path
, COMPOSE_NEWSGROUPS
, PREF_FOLDER
);
1114 mfield
= TO_FIELD_PRESENT
;
1117 * CLAWS: just don't allow return receipt request, even if the user
1118 * may want to send an email. simple but foolproof.
1120 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", FALSE
);
1122 compose_add_field_list( compose
, listAddress
);
1124 if (item
&& item
->prefs
&& item
->prefs
->compose_with_format
) {
1125 subject_format
= item
->prefs
->compose_subject_format
;
1126 body_format
= item
->prefs
->compose_body_format
;
1127 } else if (account
->compose_with_format
) {
1128 subject_format
= account
->compose_subject_format
;
1129 body_format
= account
->compose_body_format
;
1130 } else if (prefs_common
.compose_with_format
) {
1131 subject_format
= prefs_common
.compose_subject_format
;
1132 body_format
= prefs_common
.compose_body_format
;
1135 if (subject_format
|| body_format
) {
1138 && *subject_format
!= '\0' )
1140 gchar
*subject
= NULL
;
1145 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1147 /* decode \-escape sequences in the internal representation of the quote format */
1148 tmp
= g_malloc(strlen(subject_format
)+1);
1149 pref_get_unescaped_pref(tmp
, subject_format
);
1151 subject
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
1153 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
, FALSE
,
1154 compose
->gtkaspell
);
1156 quote_fmt_init(dummyinfo
, NULL
, subject
, FALSE
, compose
->account
, FALSE
);
1158 quote_fmt_scan_string(tmp
);
1161 buf
= quote_fmt_get_buffer();
1163 alertpanel_error(_("New message subject format error."));
1165 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
1166 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1167 quote_fmt_reset_vartable();
1171 mfield
= SUBJECT_FIELD_PRESENT
;
1175 && *body_format
!= '\0' )
1178 GtkTextBuffer
*buffer
;
1179 GtkTextIter start
, end
;
1183 dummyinfo
= compose_msginfo_new_from_compose(compose
);
1185 text
= GTK_TEXT_VIEW(compose
->text
);
1186 buffer
= gtk_text_view_get_buffer(text
);
1187 gtk_text_buffer_get_start_iter(buffer
, &start
);
1188 gtk_text_buffer_get_iter_at_offset(buffer
, &end
, -1);
1189 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
1191 compose_quote_fmt(compose
, dummyinfo
,
1193 NULL
, tmp
, FALSE
, TRUE
,
1194 _("The body of the \"New message\" template has an error at line %d."));
1195 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1196 quote_fmt_reset_vartable();
1200 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1201 gtkaspell_highlight_all(compose
->gtkaspell
);
1203 mfield
= BODY_FIELD_PRESENT
;
1207 procmsg_msginfo_free( &dummyinfo
);
1213 for (curr
= attach_files
; curr
!= NULL
; curr
= curr
->next
) {
1214 ainfo
= (AttachInfo
*) curr
->data
;
1215 compose_attach_append(compose
, ainfo
->file
, ainfo
->file
,
1216 ainfo
->content_type
, ainfo
->charset
);
1220 compose_show_first_last_header(compose
, TRUE
);
1222 /* Set save folder */
1223 if (item
&& item
->prefs
&& item
->prefs
->save_copy_to_folder
) {
1224 gchar
*folderidentifier
;
1226 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), prefs_common
.savemsg
);
1227 folderidentifier
= folder_item_get_identifier(item
);
1228 compose_set_save_to(compose
, folderidentifier
);
1229 g_free(folderidentifier
);
1232 /* Place cursor according to provided input (mfield) */
1234 case NO_FIELD_PRESENT
:
1235 if (compose
->header_last
)
1236 gtk_widget_grab_focus(compose
->header_last
->entry
);
1238 case TO_FIELD_PRESENT
:
1239 buf
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
1241 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
1244 gtk_widget_grab_focus(compose
->subject_entry
);
1246 case SUBJECT_FIELD_PRESENT
:
1247 textview
= GTK_TEXT_VIEW(compose
->text
);
1250 textbuf
= gtk_text_view_get_buffer(textview
);
1253 mark
= gtk_text_buffer_get_insert(textbuf
);
1254 gtk_text_buffer_get_iter_at_mark(textbuf
, &iter
, mark
);
1255 gtk_text_buffer_insert(textbuf
, &iter
, "", -1);
1257 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1258 * only defers where it comes to the variable body
1259 * is not null. If no body is present compose->text
1260 * will be null in which case you cannot place the
1261 * cursor inside the component so. An empty component
1262 * is therefore created before placing the cursor
1264 case BODY_FIELD_PRESENT
:
1265 cursor_pos
= quote_fmt_get_cursor_pos();
1266 if (cursor_pos
== -1)
1267 gtk_widget_grab_focus(compose
->header_last
->entry
);
1269 gtk_widget_grab_focus(compose
->text
);
1273 undo_unblock(compose
->undostruct
);
1275 if (prefs_common
.auto_exteditor
)
1276 compose_exec_ext_editor(compose
);
1278 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
1280 SCROLL_TO_CURSOR(compose
);
1282 compose
->modified
= FALSE
;
1283 compose_set_title(compose
);
1285 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
1290 static void compose_force_encryption(Compose
*compose
, PrefsAccount
*account
,
1291 gboolean override_pref
, const gchar
*system
)
1293 const gchar
*privacy
= NULL
;
1295 cm_return_if_fail(compose
!= NULL
);
1296 cm_return_if_fail(account
!= NULL
);
1298 if (override_pref
== FALSE
&& account
->default_encrypt_reply
== FALSE
)
1301 if (account
->default_privacy_system
&& strlen(account
->default_privacy_system
))
1302 privacy
= account
->default_privacy_system
;
1306 GSList
*privacy_avail
= privacy_get_system_ids();
1307 if (privacy_avail
&& g_slist_length(privacy_avail
)) {
1308 privacy
= (gchar
*)(privacy_avail
->data
);
1311 if (privacy
!= NULL
) {
1313 g_free(compose
->privacy_system
);
1314 compose
->privacy_system
= NULL
;
1315 g_free(compose
->encdata
);
1316 compose
->encdata
= NULL
;
1318 if (compose
->privacy_system
== NULL
)
1319 compose
->privacy_system
= g_strdup(privacy
);
1320 else if (*(compose
->privacy_system
) == '\0') {
1321 g_free(compose
->privacy_system
);
1322 g_free(compose
->encdata
);
1323 compose
->encdata
= NULL
;
1324 compose
->privacy_system
= g_strdup(privacy
);
1326 compose_update_privacy_system_menu_item(compose
, FALSE
);
1327 compose_use_encryption(compose
, TRUE
);
1331 static void compose_force_signing(Compose
*compose
, PrefsAccount
*account
, const gchar
*system
)
1333 const gchar
*privacy
= NULL
;
1335 if (account
->default_privacy_system
&& strlen(account
->default_privacy_system
))
1336 privacy
= account
->default_privacy_system
;
1340 GSList
*privacy_avail
= privacy_get_system_ids();
1341 if (privacy_avail
&& g_slist_length(privacy_avail
)) {
1342 privacy
= (gchar
*)(privacy_avail
->data
);
1346 if (privacy
!= NULL
) {
1348 g_free(compose
->privacy_system
);
1349 compose
->privacy_system
= NULL
;
1350 g_free(compose
->encdata
);
1351 compose
->encdata
= NULL
;
1353 if (compose
->privacy_system
== NULL
)
1354 compose
->privacy_system
= g_strdup(privacy
);
1355 compose_update_privacy_system_menu_item(compose
, FALSE
);
1356 compose_use_signing(compose
, TRUE
);
1360 static Compose
*compose_reply_mode(ComposeMode mode
, GSList
*msginfo_list
, gchar
*body
)
1364 Compose
*compose
= NULL
;
1366 cm_return_val_if_fail(msginfo_list
!= NULL
, NULL
);
1368 msginfo
= (MsgInfo
*)g_slist_nth_data(msginfo_list
, 0);
1369 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1371 list_len
= g_slist_length(msginfo_list
);
1375 case COMPOSE_REPLY_TO_ADDRESS
:
1376 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1377 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1379 case COMPOSE_REPLY_WITH_QUOTE
:
1380 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1381 FALSE
, prefs_common
.default_reply_list
, FALSE
, body
);
1383 case COMPOSE_REPLY_WITHOUT_QUOTE
:
1384 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1385 FALSE
, prefs_common
.default_reply_list
, FALSE
, NULL
);
1387 case COMPOSE_REPLY_TO_SENDER
:
1388 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1389 FALSE
, FALSE
, TRUE
, body
);
1391 case COMPOSE_FOLLOWUP_AND_REPLY_TO
:
1392 compose
= compose_followup_and_reply_to(msginfo
,
1393 COMPOSE_QUOTE_CHECK
,
1394 FALSE
, FALSE
, body
);
1396 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE
:
1397 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1398 FALSE
, FALSE
, TRUE
, body
);
1400 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE
:
1401 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1402 FALSE
, FALSE
, TRUE
, NULL
);
1404 case COMPOSE_REPLY_TO_ALL
:
1405 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1406 TRUE
, FALSE
, FALSE
, body
);
1408 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE
:
1409 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1410 TRUE
, FALSE
, FALSE
, body
);
1412 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE
:
1413 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1414 TRUE
, FALSE
, FALSE
, NULL
);
1416 case COMPOSE_REPLY_TO_LIST
:
1417 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_CHECK
,
1418 FALSE
, TRUE
, FALSE
, body
);
1420 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE
:
1421 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_FORCED
,
1422 FALSE
, TRUE
, FALSE
, body
);
1424 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE
:
1425 compose
= compose_reply(msginfo
, COMPOSE_QUOTE_SKIP
,
1426 FALSE
, TRUE
, FALSE
, NULL
);
1428 case COMPOSE_FORWARD
:
1429 if (prefs_common
.forward_as_attachment
) {
1430 compose
= compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH
, msginfo_list
, body
);
1433 compose
= compose_reply_mode(COMPOSE_FORWARD_INLINE
, msginfo_list
, body
);
1437 case COMPOSE_FORWARD_INLINE
:
1438 /* check if we reply to more than one Message */
1439 if (list_len
== 1) {
1440 compose
= compose_forward(NULL
, msginfo
, FALSE
, body
, FALSE
, FALSE
);
1443 /* more messages FALL THROUGH */
1444 case COMPOSE_FORWARD_AS_ATTACH
:
1445 compose
= compose_forward_multiple(NULL
, msginfo_list
);
1447 case COMPOSE_REDIRECT
:
1448 compose
= compose_redirect(NULL
, msginfo
, FALSE
);
1451 g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode
);
1454 if (compose
== NULL
) {
1455 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1459 compose
->rmode
= mode
;
1460 switch (compose
->rmode
) {
1462 case COMPOSE_REPLY_WITH_QUOTE
:
1463 case COMPOSE_REPLY_WITHOUT_QUOTE
:
1464 case COMPOSE_FOLLOWUP_AND_REPLY_TO
:
1465 debug_print("reply mode Normal\n");
1466 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/Normal", TRUE
);
1467 compose_reply_change_mode(compose
, COMPOSE_REPLY
); /* force update */
1469 case COMPOSE_REPLY_TO_SENDER
:
1470 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE
:
1471 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE
:
1472 debug_print("reply mode Sender\n");
1473 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/Sender", TRUE
);
1475 case COMPOSE_REPLY_TO_ALL
:
1476 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE
:
1477 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE
:
1478 debug_print("reply mode All\n");
1479 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/All", TRUE
);
1481 case COMPOSE_REPLY_TO_LIST
:
1482 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE
:
1483 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE
:
1484 debug_print("reply mode List\n");
1485 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/ReplyMode/List", TRUE
);
1487 case COMPOSE_REPLY_TO_ADDRESS
:
1488 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/ReplyMode", FALSE
);
1496 static Compose
*compose_reply(MsgInfo
*msginfo
,
1497 ComposeQuoteMode quote_mode
,
1503 return compose_generic_reply(msginfo
, quote_mode
, to_all
, to_ml
,
1504 to_sender
, FALSE
, body
);
1507 static Compose
*compose_followup_and_reply_to(MsgInfo
*msginfo
,
1508 ComposeQuoteMode quote_mode
,
1513 return compose_generic_reply(msginfo
, quote_mode
, to_all
, FALSE
,
1514 to_sender
, TRUE
, body
);
1517 static void compose_extract_original_charset(Compose
*compose
)
1519 MsgInfo
*info
= NULL
;
1520 if (compose
->replyinfo
) {
1521 info
= compose
->replyinfo
;
1522 } else if (compose
->fwdinfo
) {
1523 info
= compose
->fwdinfo
;
1524 } else if (compose
->targetinfo
) {
1525 info
= compose
->targetinfo
;
1528 MimeInfo
*mimeinfo
= procmime_scan_message_short(info
);
1529 MimeInfo
*partinfo
= mimeinfo
;
1530 while (partinfo
&& partinfo
->type
!= MIMETYPE_TEXT
)
1531 partinfo
= procmime_mimeinfo_next(partinfo
);
1533 compose
->orig_charset
=
1534 g_strdup(procmime_mimeinfo_get_parameter(
1535 partinfo
, "charset"));
1537 procmime_mimeinfo_free_all(&mimeinfo
);
1541 #define SIGNAL_BLOCK(buffer) { \
1542 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1543 G_CALLBACK(compose_changed_cb), \
1545 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1546 G_CALLBACK(text_inserted), \
1550 #define SIGNAL_UNBLOCK(buffer) { \
1551 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1552 G_CALLBACK(compose_changed_cb), \
1554 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1555 G_CALLBACK(text_inserted), \
1559 static Compose
*compose_generic_reply(MsgInfo
*msginfo
,
1560 ComposeQuoteMode quote_mode
,
1561 gboolean to_all
, gboolean to_ml
,
1563 gboolean followup_and_reply_to
,
1567 PrefsAccount
*account
= NULL
;
1568 GtkTextView
*textview
;
1569 GtkTextBuffer
*textbuf
;
1570 gboolean quote
= FALSE
;
1571 const gchar
*qmark
= NULL
;
1572 const gchar
*body_fmt
= NULL
;
1573 gchar
*s_system
= NULL
;
1575 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1576 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
1578 account
= account_get_reply_account(msginfo
, prefs_common
.reply_account_autosel
);
1580 cm_return_val_if_fail(account
!= NULL
, NULL
);
1582 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REPLY
, FALSE
);
1584 compose
->updating
= TRUE
;
1586 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", FALSE
);
1587 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", TRUE
);
1589 compose
->replyinfo
= procmsg_msginfo_get_full_info(msginfo
);
1590 if (!compose
->replyinfo
)
1591 compose
->replyinfo
= procmsg_msginfo_copy(msginfo
);
1593 compose_extract_original_charset(compose
);
1595 if (msginfo
->folder
&& msginfo
->folder
->ret_rcpt
)
1596 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
1598 /* Set save folder */
1599 if (msginfo
->folder
&& msginfo
->folder
->prefs
&& msginfo
->folder
->prefs
->save_copy_to_folder
) {
1600 gchar
*folderidentifier
;
1602 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
1603 folderidentifier
= folder_item_get_identifier(msginfo
->folder
);
1604 compose_set_save_to(compose
, folderidentifier
);
1605 g_free(folderidentifier
);
1608 if (compose_parse_header(compose
, msginfo
) < 0) {
1609 compose
->updating
= FALSE
;
1610 compose_destroy(compose
);
1614 /* override from name according to folder properties */
1615 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1616 msginfo
->folder
->prefs
->reply_with_format
&&
1617 msginfo
->folder
->prefs
->reply_override_from_format
&&
1618 *msginfo
->folder
->prefs
->reply_override_from_format
!= '\0') {
1623 /* decode \-escape sequences in the internal representation of the quote format */
1624 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->reply_override_from_format
)+1);
1625 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->reply_override_from_format
);
1628 quote_fmt_init(compose
->replyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1629 compose
->gtkaspell
);
1631 quote_fmt_init(compose
->replyinfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1633 quote_fmt_scan_string(tmp
);
1636 buf
= quote_fmt_get_buffer();
1638 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1640 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1641 quote_fmt_reset_vartable();
1646 textview
= (GTK_TEXT_VIEW(compose
->text
));
1647 textbuf
= gtk_text_view_get_buffer(textview
);
1648 compose_create_tags(textview
, compose
);
1650 undo_block(compose
->undostruct
);
1652 compose_set_dictionaries_from_folder_prefs(compose
, msginfo
->folder
);
1653 gtkaspell_block_check(compose
->gtkaspell
);
1656 if (quote_mode
== COMPOSE_QUOTE_FORCED
||
1657 (quote_mode
== COMPOSE_QUOTE_CHECK
&& prefs_common
.reply_with_quote
)) {
1658 /* use the reply format of folder (if enabled), or the account's one
1659 (if enabled) or fallback to the global reply format, which is always
1660 enabled (even if empty), and use the relevant quotemark */
1662 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1663 msginfo
->folder
->prefs
->reply_with_format
) {
1664 qmark
= msginfo
->folder
->prefs
->reply_quotemark
;
1665 body_fmt
= msginfo
->folder
->prefs
->reply_body_format
;
1667 } else if (account
->reply_with_format
) {
1668 qmark
= account
->reply_quotemark
;
1669 body_fmt
= account
->reply_body_format
;
1672 qmark
= prefs_common
.quotemark
;
1673 if (prefs_common
.quotefmt
&& *prefs_common
.quotefmt
)
1674 body_fmt
= gettext(prefs_common
.quotefmt
);
1681 /* empty quotemark is not allowed */
1682 if (qmark
== NULL
|| *qmark
== '\0')
1684 compose_quote_fmt(compose
, compose
->replyinfo
,
1685 body_fmt
, qmark
, body
, FALSE
, TRUE
,
1686 _("The body of the \"Reply\" template has an error at line %d."));
1687 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1688 quote_fmt_reset_vartable();
1691 if (MSG_IS_ENCRYPTED(compose
->replyinfo
->flags
)) {
1692 compose_force_encryption(compose
, account
, FALSE
, s_system
);
1695 privacy_msginfo_get_signed_state(compose
->replyinfo
, &s_system
);
1696 if (MSG_IS_SIGNED(compose
->replyinfo
->flags
) && account
->default_sign_reply
) {
1697 compose_force_signing(compose
, account
, s_system
);
1701 SIGNAL_BLOCK(textbuf
);
1703 if (account
->auto_sig
)
1704 compose_insert_sig(compose
, FALSE
);
1706 compose_wrap_all(compose
);
1709 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1710 gtkaspell_highlight_all(compose
->gtkaspell
);
1711 gtkaspell_unblock_check(compose
->gtkaspell
);
1713 SIGNAL_UNBLOCK(textbuf
);
1715 gtk_widget_grab_focus(compose
->text
);
1717 undo_unblock(compose
->undostruct
);
1719 if (prefs_common
.auto_exteditor
)
1720 compose_exec_ext_editor(compose
);
1722 compose
->modified
= FALSE
;
1723 compose_set_title(compose
);
1725 compose
->updating
= FALSE
;
1726 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
1727 SCROLL_TO_CURSOR(compose
);
1729 if (compose
->deferred_destroy
) {
1730 compose_destroy(compose
);
1738 #define INSERT_FW_HEADER(var, hdr) \
1739 if (msginfo->var && *msginfo->var) { \
1740 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1741 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1742 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1745 Compose
*compose_forward(PrefsAccount
*account
, MsgInfo
*msginfo
,
1746 gboolean as_attach
, const gchar
*body
,
1747 gboolean no_extedit
,
1751 GtkTextView
*textview
;
1752 GtkTextBuffer
*textbuf
;
1753 gint cursor_pos
= -1;
1756 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
1757 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
1760 !(account
= compose_guess_forward_account_from_msginfo
1762 account
= cur_account
;
1764 if (!prefs_common
.forward_as_attachment
)
1765 mode
= COMPOSE_FORWARD_INLINE
;
1767 mode
= COMPOSE_FORWARD
;
1768 compose
= compose_create(account
, msginfo
->folder
, mode
, batch
);
1770 compose
->updating
= TRUE
;
1771 compose
->fwdinfo
= procmsg_msginfo_get_full_info(msginfo
);
1772 if (!compose
->fwdinfo
)
1773 compose
->fwdinfo
= procmsg_msginfo_copy(msginfo
);
1775 compose_extract_original_charset(compose
);
1777 if (msginfo
->subject
&& *msginfo
->subject
) {
1778 gchar
*buf
, *buf2
, *p
;
1780 buf
= p
= g_strdup(msginfo
->subject
);
1781 p
+= subject_get_prefix_length(p
);
1782 memmove(buf
, p
, strlen(p
) + 1);
1784 buf2
= g_strdup_printf("Fw: %s", buf
);
1785 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
1791 /* override from name according to folder properties */
1792 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1793 msginfo
->folder
->prefs
->forward_with_format
&&
1794 msginfo
->folder
->prefs
->forward_override_from_format
&&
1795 *msginfo
->folder
->prefs
->forward_override_from_format
!= '\0') {
1799 MsgInfo
*full_msginfo
= NULL
;
1802 full_msginfo
= procmsg_msginfo_get_full_info(msginfo
);
1804 full_msginfo
= procmsg_msginfo_copy(msginfo
);
1806 /* decode \-escape sequences in the internal representation of the quote format */
1807 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->forward_override_from_format
)+1);
1808 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->forward_override_from_format
);
1811 gtkaspell_block_check(compose
->gtkaspell
);
1812 quote_fmt_init(full_msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
1813 compose
->gtkaspell
);
1815 quote_fmt_init(full_msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
1817 quote_fmt_scan_string(tmp
);
1820 buf
= quote_fmt_get_buffer();
1822 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1824 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
1825 quote_fmt_reset_vartable();
1828 procmsg_msginfo_free(&full_msginfo
);
1831 textview
= GTK_TEXT_VIEW(compose
->text
);
1832 textbuf
= gtk_text_view_get_buffer(textview
);
1833 compose_create_tags(textview
, compose
);
1835 undo_block(compose
->undostruct
);
1839 msgfile
= procmsg_get_message_file(msginfo
);
1840 if (!is_file_exist(msgfile
))
1841 g_warning("%s: file does not exist", msgfile
);
1843 compose_attach_append(compose
, msgfile
, msgfile
,
1844 "message/rfc822", NULL
);
1848 const gchar
*qmark
= NULL
;
1849 const gchar
*body_fmt
= NULL
;
1850 MsgInfo
*full_msginfo
;
1852 full_msginfo
= procmsg_msginfo_get_full_info(msginfo
);
1854 full_msginfo
= procmsg_msginfo_copy(msginfo
);
1856 /* use the forward format of folder (if enabled), or the account's one
1857 (if enabled) or fallback to the global forward format, which is always
1858 enabled (even if empty), and use the relevant quotemark */
1859 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1860 msginfo
->folder
->prefs
->forward_with_format
) {
1861 qmark
= msginfo
->folder
->prefs
->forward_quotemark
;
1862 body_fmt
= msginfo
->folder
->prefs
->forward_body_format
;
1864 } else if (account
->forward_with_format
) {
1865 qmark
= account
->forward_quotemark
;
1866 body_fmt
= account
->forward_body_format
;
1869 qmark
= prefs_common
.fw_quotemark
;
1870 if (prefs_common
.fw_quotefmt
&& *prefs_common
.fw_quotefmt
)
1871 body_fmt
= gettext(prefs_common
.fw_quotefmt
);
1876 /* empty quotemark is not allowed */
1877 if (qmark
== NULL
|| *qmark
== '\0')
1880 compose_quote_fmt(compose
, full_msginfo
,
1881 body_fmt
, qmark
, body
, FALSE
, TRUE
,
1882 _("The body of the \"Forward\" template has an error at line %d."));
1883 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
1884 quote_fmt_reset_vartable();
1885 compose_attach_parts(compose
, msginfo
);
1887 procmsg_msginfo_free(&full_msginfo
);
1890 SIGNAL_BLOCK(textbuf
);
1892 if (account
->auto_sig
)
1893 compose_insert_sig(compose
, FALSE
);
1895 compose_wrap_all(compose
);
1898 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
1899 gtkaspell_highlight_all(compose
->gtkaspell
);
1900 gtkaspell_unblock_check(compose
->gtkaspell
);
1902 SIGNAL_UNBLOCK(textbuf
);
1904 cursor_pos
= quote_fmt_get_cursor_pos();
1905 if (cursor_pos
== -1)
1906 gtk_widget_grab_focus(compose
->header_last
->entry
);
1908 gtk_widget_grab_focus(compose
->text
);
1910 if (!no_extedit
&& prefs_common
.auto_exteditor
)
1911 compose_exec_ext_editor(compose
);
1914 if (msginfo
->folder
&& msginfo
->folder
->prefs
&& msginfo
->folder
->prefs
->save_copy_to_folder
) {
1915 gchar
*folderidentifier
;
1917 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
1918 folderidentifier
= folder_item_get_identifier(msginfo
->folder
);
1919 compose_set_save_to(compose
, folderidentifier
);
1920 g_free(folderidentifier
);
1923 undo_unblock(compose
->undostruct
);
1925 compose
->modified
= FALSE
;
1926 compose_set_title(compose
);
1928 compose
->updating
= FALSE
;
1929 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
1930 SCROLL_TO_CURSOR(compose
);
1932 if (compose
->deferred_destroy
) {
1933 compose_destroy(compose
);
1937 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
1942 #undef INSERT_FW_HEADER
1944 static Compose
*compose_forward_multiple(PrefsAccount
*account
, GSList
*msginfo_list
)
1947 GtkTextView
*textview
;
1948 GtkTextBuffer
*textbuf
;
1952 gboolean single_mail
= TRUE
;
1954 cm_return_val_if_fail(msginfo_list
!= NULL
, NULL
);
1956 if (g_slist_length(msginfo_list
) > 1)
1957 single_mail
= FALSE
;
1959 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
)
1960 if (((MsgInfo
*)msginfo
->data
)->folder
== NULL
)
1963 /* guess account from first selected message */
1965 !(account
= compose_guess_forward_account_from_msginfo
1966 (msginfo_list
->data
)))
1967 account
= cur_account
;
1969 cm_return_val_if_fail(account
!= NULL
, NULL
);
1971 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
) {
1972 if (msginfo
->data
) {
1973 MSG_UNSET_PERM_FLAGS(((MsgInfo
*)msginfo
->data
)->flags
, MSG_REPLIED
);
1974 MSG_SET_PERM_FLAGS(((MsgInfo
*)msginfo
->data
)->flags
, MSG_FORWARDED
);
1978 if (msginfo_list
== NULL
|| msginfo_list
->data
== NULL
) {
1979 g_warning("no msginfo_list");
1983 compose
= compose_create(account
, ((MsgInfo
*)msginfo_list
->data
)->folder
, COMPOSE_FORWARD
, FALSE
);
1985 compose
->updating
= TRUE
;
1987 /* override from name according to folder properties */
1988 if (msginfo_list
->data
) {
1989 MsgInfo
*msginfo
= msginfo_list
->data
;
1991 if (msginfo
->folder
&& msginfo
->folder
->prefs
&&
1992 msginfo
->folder
->prefs
->forward_with_format
&&
1993 msginfo
->folder
->prefs
->forward_override_from_format
&&
1994 *msginfo
->folder
->prefs
->forward_override_from_format
!= '\0') {
1999 /* decode \-escape sequences in the internal representation of the quote format */
2000 tmp
= g_malloc(strlen(msginfo
->folder
->prefs
->forward_override_from_format
)+1);
2001 pref_get_unescaped_pref(tmp
, msginfo
->folder
->prefs
->forward_override_from_format
);
2004 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
2005 compose
->gtkaspell
);
2007 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
2009 quote_fmt_scan_string(tmp
);
2012 buf
= quote_fmt_get_buffer();
2014 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
2016 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
2017 quote_fmt_reset_vartable();
2023 textview
= GTK_TEXT_VIEW(compose
->text
);
2024 textbuf
= gtk_text_view_get_buffer(textview
);
2025 compose_create_tags(textview
, compose
);
2027 undo_block(compose
->undostruct
);
2028 for (msginfo
= msginfo_list
; msginfo
!= NULL
; msginfo
= msginfo
->next
) {
2029 msgfile
= procmsg_get_message_file((MsgInfo
*)msginfo
->data
);
2031 if (!is_file_exist(msgfile
))
2032 g_warning("%s: file does not exist", msgfile
);
2034 compose_attach_append(compose
, msgfile
, msgfile
,
2035 "message/rfc822", NULL
);
2040 MsgInfo
*info
= (MsgInfo
*)msginfo_list
->data
;
2041 if (info
->subject
&& *info
->subject
) {
2042 gchar
*buf
, *buf2
, *p
;
2044 buf
= p
= g_strdup(info
->subject
);
2045 p
+= subject_get_prefix_length(p
);
2046 memmove(buf
, p
, strlen(p
) + 1);
2048 buf2
= g_strdup_printf("Fw: %s", buf
);
2049 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
2055 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
),
2056 _("Fw: multiple emails"));
2059 SIGNAL_BLOCK(textbuf
);
2061 if (account
->auto_sig
)
2062 compose_insert_sig(compose
, FALSE
);
2064 compose_wrap_all(compose
);
2066 SIGNAL_UNBLOCK(textbuf
);
2068 gtk_text_buffer_get_start_iter(textbuf
, &iter
);
2069 gtk_text_buffer_place_cursor(textbuf
, &iter
);
2071 if (prefs_common
.auto_exteditor
)
2072 compose_exec_ext_editor(compose
);
2074 gtk_widget_grab_focus(compose
->header_last
->entry
);
2075 undo_unblock(compose
->undostruct
);
2076 compose
->modified
= FALSE
;
2077 compose_set_title(compose
);
2079 compose
->updating
= FALSE
;
2080 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2081 SCROLL_TO_CURSOR(compose
);
2083 if (compose
->deferred_destroy
) {
2084 compose_destroy(compose
);
2088 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2093 static gboolean
compose_is_sig_separator(Compose
*compose
, GtkTextBuffer
*textbuf
, GtkTextIter
*iter
)
2095 GtkTextIter start
= *iter
;
2096 GtkTextIter end_iter
;
2097 int start_pos
= gtk_text_iter_get_offset(&start
);
2099 if (!compose
->account
->sig_sep
)
2102 gtk_text_buffer_get_iter_at_offset(textbuf
, &end_iter
,
2103 start_pos
+strlen(compose
->account
->sig_sep
));
2105 /* check sig separator */
2106 str
= gtk_text_iter_get_text(&start
, &end_iter
);
2107 if (!strcmp(str
, compose
->account
->sig_sep
)) {
2109 /* check end of line (\n) */
2110 gtk_text_buffer_get_iter_at_offset(textbuf
, &start
,
2111 start_pos
+strlen(compose
->account
->sig_sep
));
2112 gtk_text_buffer_get_iter_at_offset(textbuf
, &end_iter
,
2113 start_pos
+strlen(compose
->account
->sig_sep
)+1);
2114 tmp
= gtk_text_iter_get_text(&start
, &end_iter
);
2115 if (!strcmp(tmp
,"\n")) {
2127 static gboolean
compose_update_folder_hook(gpointer source
, gpointer data
)
2129 FolderUpdateData
*hookdata
= (FolderUpdateData
*)source
;
2130 Compose
*compose
= (Compose
*)data
;
2131 FolderItem
*old_item
= NULL
;
2132 FolderItem
*new_item
= NULL
;
2133 gchar
*old_id
, *new_id
;
2135 if (!(hookdata
->update_flags
& FOLDER_REMOVE_FOLDERITEM
)
2136 && !(hookdata
->update_flags
& FOLDER_MOVE_FOLDERITEM
))
2139 old_item
= hookdata
->item
;
2140 new_item
= hookdata
->item2
;
2142 old_id
= folder_item_get_identifier(old_item
);
2143 new_id
= new_item
? folder_item_get_identifier(new_item
) : g_strdup("NULL");
2145 if (compose
->targetinfo
&& compose
->targetinfo
->folder
== old_item
) {
2146 debug_print("updating targetinfo folder: %s -> %s\n", old_id
, new_id
);
2147 compose
->targetinfo
->folder
= new_item
;
2150 if (compose
->replyinfo
&& compose
->replyinfo
->folder
== old_item
) {
2151 debug_print("updating replyinfo folder: %s -> %s\n", old_id
, new_id
);
2152 compose
->replyinfo
->folder
= new_item
;
2155 if (compose
->fwdinfo
&& compose
->fwdinfo
->folder
== old_item
) {
2156 debug_print("updating fwdinfo folder: %s -> %s\n", old_id
, new_id
);
2157 compose
->fwdinfo
->folder
= new_item
;
2165 static void compose_colorize_signature(Compose
*compose
)
2167 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
2169 GtkTextIter end_iter
;
2170 gtk_text_buffer_get_start_iter(buffer
, &iter
);
2171 while (gtk_text_iter_forward_line(&iter
))
2172 if (compose_is_sig_separator(compose
, buffer
, &iter
)) {
2173 gtk_text_buffer_get_end_iter(buffer
, &end_iter
);
2174 gtk_text_buffer_apply_tag_by_name(buffer
,"signature",&iter
, &end_iter
);
2178 #define BLOCK_WRAP() { \
2179 prev_autowrap = compose->autowrap; \
2180 buffer = gtk_text_view_get_buffer( \
2181 GTK_TEXT_VIEW(compose->text)); \
2182 compose->autowrap = FALSE; \
2184 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2185 G_CALLBACK(compose_changed_cb), \
2187 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2188 G_CALLBACK(text_inserted), \
2191 #define UNBLOCK_WRAP() { \
2192 compose->autowrap = prev_autowrap; \
2193 if (compose->autowrap) { \
2194 gint old = compose->draft_timeout_tag; \
2195 compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; \
2196 compose_wrap_all(compose); \
2197 compose->draft_timeout_tag = old; \
2200 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2201 G_CALLBACK(compose_changed_cb), \
2203 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2204 G_CALLBACK(text_inserted), \
2208 Compose
*compose_reedit(MsgInfo
*msginfo
, gboolean batch
)
2210 Compose
*compose
= NULL
;
2211 PrefsAccount
*account
= NULL
;
2212 GtkTextView
*textview
;
2213 GtkTextBuffer
*textbuf
;
2217 gchar buf
[BUFFSIZE
];
2218 gboolean use_signing
= FALSE
;
2219 gboolean use_encryption
= FALSE
;
2220 gchar
*privacy_system
= NULL
;
2221 int priority
= PRIORITY_NORMAL
;
2222 MsgInfo
*replyinfo
= NULL
, *fwdinfo
= NULL
;
2223 gboolean autowrap
= prefs_common
.autowrap
;
2224 gboolean autoindent
= prefs_common
.auto_indent
;
2225 HeaderEntry
*manual_headers
= NULL
;
2227 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
2228 cm_return_val_if_fail(msginfo
->folder
!= NULL
, NULL
);
2230 if (compose_put_existing_to_front(msginfo
)) {
2234 if (folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) ||
2235 folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
) ||
2236 folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
)) {
2237 gchar queueheader_buf
[BUFFSIZE
];
2240 /* Select Account from queue headers */
2241 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2242 sizeof(queueheader_buf
), "X-Claws-Account-Id:")) {
2243 id
= atoi(&queueheader_buf
[strlen("X-Claws-Account-Id:")]);
2244 account
= account_find_from_id(id
);
2246 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2247 sizeof(queueheader_buf
), "X-Sylpheed-Account-Id:")) {
2248 id
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Account-Id:")]);
2249 account
= account_find_from_id(id
);
2251 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2252 sizeof(queueheader_buf
), "NAID:")) {
2253 id
= atoi(&queueheader_buf
[strlen("NAID:")]);
2254 account
= account_find_from_id(id
);
2256 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2257 sizeof(queueheader_buf
), "MAID:")) {
2258 id
= atoi(&queueheader_buf
[strlen("MAID:")]);
2259 account
= account_find_from_id(id
);
2261 if (!account
&& !procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2262 sizeof(queueheader_buf
), "S:")) {
2263 account
= account_find_from_address(queueheader_buf
, FALSE
);
2265 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2266 sizeof(queueheader_buf
), "X-Claws-Sign:")) {
2267 param
= atoi(&queueheader_buf
[strlen("X-Claws-Sign:")]);
2268 use_signing
= param
;
2271 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2272 sizeof(queueheader_buf
), "X-Sylpheed-Sign:")) {
2273 param
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Sign:")]);
2274 use_signing
= param
;
2277 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2278 sizeof(queueheader_buf
), "X-Claws-Encrypt:")) {
2279 param
= atoi(&queueheader_buf
[strlen("X-Claws-Encrypt:")]);
2280 use_encryption
= param
;
2282 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2283 sizeof(queueheader_buf
), "X-Sylpheed-Encrypt:")) {
2284 param
= atoi(&queueheader_buf
[strlen("X-Sylpheed-Encrypt:")]);
2285 use_encryption
= param
;
2287 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2288 sizeof(queueheader_buf
), "X-Claws-Auto-Wrapping:")) {
2289 param
= atoi(&queueheader_buf
[strlen("X-Claws-Auto-Wrapping:")]);
2292 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2293 sizeof(queueheader_buf
), "X-Claws-Auto-Indent:")) {
2294 param
= atoi(&queueheader_buf
[strlen("X-Claws-Auto-Indent:")]);
2297 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2298 sizeof(queueheader_buf
), "X-Claws-Privacy-System:")) {
2299 privacy_system
= g_strdup(&queueheader_buf
[strlen("X-Claws-Privacy-System:")]);
2301 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2302 sizeof(queueheader_buf
), "X-Sylpheed-Privacy-System:")) {
2303 privacy_system
= g_strdup(&queueheader_buf
[strlen("X-Sylpheed-Privacy-System:")]);
2305 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2306 sizeof(queueheader_buf
), "X-Priority: ")) {
2307 param
= atoi(&queueheader_buf
[strlen("X-Priority: ")]); /* mind the space */
2310 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2311 sizeof(queueheader_buf
), "RMID:")) {
2312 gchar
**tokens
= g_strsplit(&queueheader_buf
[strlen("RMID:")], "\t", 0);
2313 if (tokens
[0] && tokens
[1] && tokens
[2]) {
2314 FolderItem
*orig_item
= folder_find_item_from_identifier(tokens
[0]);
2315 if (orig_item
!= NULL
) {
2316 replyinfo
= folder_item_get_msginfo_by_msgid(orig_item
, tokens
[2]);
2321 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
,
2322 sizeof(queueheader_buf
), "FMID:")) {
2323 gchar
**tokens
= g_strsplit(&queueheader_buf
[strlen("FMID:")], "\t", 0);
2324 if (tokens
[0] && tokens
[1] && tokens
[2]) {
2325 FolderItem
*orig_item
= folder_find_item_from_identifier(tokens
[0]);
2326 if (orig_item
!= NULL
) {
2327 fwdinfo
= folder_item_get_msginfo_by_msgid(orig_item
, tokens
[2]);
2332 /* Get manual headers */
2333 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
, sizeof(queueheader_buf
), "X-Claws-Manual-Headers:")) {
2334 gchar
*listmh
= g_strdup(&queueheader_buf
[strlen("X-Claws-Manual-Headers:")]);
2335 if (*listmh
!= '\0') {
2336 debug_print("Got manual headers: %s\n", listmh
);
2337 manual_headers
= procheader_entries_from_str(listmh
);
2342 account
= msginfo
->folder
->folder
->account
;
2345 if (!account
&& prefs_common
.reedit_account_autosel
) {
2346 gchar from
[BUFFSIZE
];
2347 if (!procheader_get_header_from_msginfo(msginfo
, from
, sizeof(from
), "FROM:")) {
2348 extract_address(from
);
2349 account
= account_find_from_address(from
, FALSE
);
2353 account
= cur_account
;
2355 cm_return_val_if_fail(account
!= NULL
, NULL
);
2357 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REEDIT
, batch
);
2359 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoWrap", autowrap
);
2360 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoIndent", autoindent
);
2361 compose
->autowrap
= autowrap
;
2362 compose
->replyinfo
= replyinfo
;
2363 compose
->fwdinfo
= fwdinfo
;
2365 compose
->updating
= TRUE
;
2366 compose
->priority
= priority
;
2368 if (privacy_system
!= NULL
) {
2369 compose
->privacy_system
= privacy_system
;
2370 compose_use_signing(compose
, use_signing
);
2371 compose_use_encryption(compose
, use_encryption
);
2372 compose_update_privacy_system_menu_item(compose
, FALSE
);
2374 activate_privacy_system(compose
, account
, FALSE
);
2377 compose
->targetinfo
= procmsg_msginfo_copy(msginfo
);
2379 compose_extract_original_charset(compose
);
2381 if (folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) ||
2382 folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
) ||
2383 folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
)) {
2384 gchar queueheader_buf
[BUFFSIZE
];
2386 /* Set message save folder */
2387 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
, sizeof(queueheader_buf
), "SCF:")) {
2388 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), TRUE
);
2389 compose_set_save_to(compose
, &queueheader_buf
[4]);
2391 if (!procheader_get_header_from_msginfo(msginfo
, queueheader_buf
, sizeof(queueheader_buf
), "RRCPT:")) {
2392 gint active
= atoi(&queueheader_buf
[strlen("RRCPT:")]);
2394 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/RequestRetRcpt", TRUE
);
2399 if (compose_parse_header(compose
, msginfo
) < 0) {
2400 compose
->updating
= FALSE
;
2401 compose_destroy(compose
);
2404 compose_reedit_set_entry(compose
, msginfo
);
2406 textview
= GTK_TEXT_VIEW(compose
->text
);
2407 textbuf
= gtk_text_view_get_buffer(textview
);
2408 compose_create_tags(textview
, compose
);
2410 mark
= gtk_text_buffer_get_insert(textbuf
);
2411 gtk_text_buffer_get_iter_at_mark(textbuf
, &iter
, mark
);
2413 g_signal_handlers_block_by_func(G_OBJECT(textbuf
),
2414 G_CALLBACK(compose_changed_cb
),
2417 if (MSG_IS_ENCRYPTED(msginfo
->flags
)) {
2418 fp
= procmime_get_first_encrypted_text_content(msginfo
);
2420 compose_force_encryption(compose
, account
, TRUE
, NULL
);
2423 fp
= procmime_get_first_text_content(msginfo
);
2426 g_warning("Can't get text part");
2430 gboolean prev_autowrap
;
2431 GtkTextBuffer
*buffer
;
2433 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
2435 gtk_text_buffer_insert(textbuf
, &iter
, buf
, -1);
2441 compose_attach_parts(compose
, msginfo
);
2443 compose_colorize_signature(compose
);
2445 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf
),
2446 G_CALLBACK(compose_changed_cb
),
2449 if (manual_headers
!= NULL
) {
2450 if (compose_parse_manual_headers(compose
, msginfo
, manual_headers
) < 0) {
2451 procheader_entries_free(manual_headers
);
2452 compose
->updating
= FALSE
;
2453 compose_destroy(compose
);
2456 procheader_entries_free(manual_headers
);
2459 gtk_widget_grab_focus(compose
->text
);
2461 if (prefs_common
.auto_exteditor
) {
2462 compose_exec_ext_editor(compose
);
2464 compose
->modified
= FALSE
;
2465 compose_set_title(compose
);
2467 compose
->updating
= FALSE
;
2468 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2469 SCROLL_TO_CURSOR(compose
);
2471 if (compose
->deferred_destroy
) {
2472 compose_destroy(compose
);
2476 compose
->sig_str
= account_get_signature_str(compose
->account
);
2478 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2483 Compose
*compose_redirect(PrefsAccount
*account
, MsgInfo
*msginfo
,
2490 cm_return_val_if_fail(msginfo
!= NULL
, NULL
);
2493 account
= account_get_reply_account(msginfo
,
2494 prefs_common
.reply_account_autosel
);
2495 cm_return_val_if_fail(account
!= NULL
, NULL
);
2497 compose
= compose_create(account
, msginfo
->folder
, COMPOSE_REDIRECT
, batch
);
2499 compose
->updating
= TRUE
;
2501 compose_create_tags(GTK_TEXT_VIEW(compose
->text
), compose
);
2502 compose
->replyinfo
= NULL
;
2503 compose
->fwdinfo
= NULL
;
2505 compose_show_first_last_header(compose
, TRUE
);
2507 gtk_widget_grab_focus(compose
->header_last
->entry
);
2509 filename
= procmsg_get_message_file(msginfo
);
2511 if (filename
== NULL
) {
2512 compose
->updating
= FALSE
;
2513 compose_destroy(compose
);
2518 compose
->redirect_filename
= filename
;
2520 /* Set save folder */
2521 item
= msginfo
->folder
;
2522 if (item
&& item
->prefs
&& item
->prefs
->save_copy_to_folder
) {
2523 gchar
*folderidentifier
;
2525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), prefs_common
.savemsg
);
2526 folderidentifier
= folder_item_get_identifier(item
);
2527 compose_set_save_to(compose
, folderidentifier
);
2528 g_free(folderidentifier
);
2531 compose_attach_parts(compose
, msginfo
);
2533 if (msginfo
->subject
)
2534 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
),
2536 gtk_editable_set_editable(GTK_EDITABLE(compose
->subject_entry
), FALSE
);
2538 compose_quote_fmt(compose
, msginfo
, "%M", NULL
, NULL
, FALSE
, FALSE
,
2539 _("The body of the \"Redirect\" template has an error at line %d."));
2540 quote_fmt_reset_vartable();
2541 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose
->text
), FALSE
);
2543 compose_colorize_signature(compose
);
2546 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Add", FALSE
);
2547 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Remove", FALSE
);
2548 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Properties", FALSE
);
2550 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/Save", FALSE
);
2551 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/InsertFile", FALSE
);
2552 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/AttachFile", FALSE
);
2553 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/InsertSig", FALSE
);
2554 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message/ReplaceSig", FALSE
);
2555 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit", FALSE
);
2556 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options", FALSE
);
2557 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/ShowRuler", FALSE
);
2558 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/Actions", FALSE
);
2560 if (compose
->toolbar
->draft_btn
)
2561 gtk_widget_set_sensitive(compose
->toolbar
->draft_btn
, FALSE
);
2562 if (compose
->toolbar
->insert_btn
)
2563 gtk_widget_set_sensitive(compose
->toolbar
->insert_btn
, FALSE
);
2564 if (compose
->toolbar
->attach_btn
)
2565 gtk_widget_set_sensitive(compose
->toolbar
->attach_btn
, FALSE
);
2566 if (compose
->toolbar
->sig_btn
)
2567 gtk_widget_set_sensitive(compose
->toolbar
->sig_btn
, FALSE
);
2568 if (compose
->toolbar
->exteditor_btn
)
2569 gtk_widget_set_sensitive(compose
->toolbar
->exteditor_btn
, FALSE
);
2570 if (compose
->toolbar
->linewrap_current_btn
)
2571 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_current_btn
, FALSE
);
2572 if (compose
->toolbar
->linewrap_all_btn
)
2573 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_all_btn
, FALSE
);
2575 compose
->modified
= FALSE
;
2576 compose_set_title(compose
);
2577 compose
->updating
= FALSE
;
2578 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
; /* desinhibit auto-drafting after loading */
2579 SCROLL_TO_CURSOR(compose
);
2581 if (compose
->deferred_destroy
) {
2582 compose_destroy(compose
);
2586 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
2591 const GList
*compose_get_compose_list(void)
2593 return compose_list
;
2596 void compose_entry_append(Compose
*compose
, const gchar
*address
,
2597 ComposeEntryType type
, ComposePrefType pref_type
)
2599 const gchar
*header
;
2601 gboolean in_quote
= FALSE
;
2602 if (!address
|| *address
== '\0') return;
2609 header
= N_("Bcc:");
2611 case COMPOSE_REPLYTO
:
2612 header
= N_("Reply-To:");
2614 case COMPOSE_NEWSGROUPS
:
2615 header
= N_("Newsgroups:");
2617 case COMPOSE_FOLLOWUPTO
:
2618 header
= N_( "Followup-To:");
2620 case COMPOSE_INREPLYTO
:
2621 header
= N_( "In-Reply-To:");
2628 header
= prefs_common_translated_header_name(header
);
2630 cur
= begin
= (gchar
*)address
;
2632 /* we separate the line by commas, but not if we're inside a quoted
2634 while (*cur
!= '\0') {
2636 in_quote
= !in_quote
;
2637 if (*cur
== ',' && !in_quote
) {
2638 gchar
*tmp
= g_strdup(begin
);
2640 tmp
[cur
-begin
]='\0';
2643 while (*tmp
== ' ' || *tmp
== '\t')
2645 compose_add_header_entry(compose
, header
, tmp
, pref_type
);
2652 gchar
*tmp
= g_strdup(begin
);
2654 tmp
[cur
-begin
]='\0';
2655 while (*tmp
== ' ' || *tmp
== '\t')
2657 compose_add_header_entry(compose
, header
, tmp
, pref_type
);
2662 static void compose_entry_mark_default_to(Compose
*compose
, const gchar
*mailto
)
2664 #if !GTK_CHECK_VERSION(3, 0, 0)
2665 static GdkColor yellow
;
2666 static GdkColor black
;
2667 static gboolean yellow_initialised
= FALSE
;
2669 static GdkColor yellow
= { (guint32
)0, (guint16
)0xf5, (guint16
)0xf6, (guint16
)0xbe };
2670 static GdkColor black
= { (guint32
)0, (guint16
)0x0, (guint16
)0x0, (guint16
)0x0 };
2675 #if !GTK_CHECK_VERSION(3, 0, 0)
2676 if (!yellow_initialised
) {
2677 gdk_color_parse("#f5f6be", &yellow
);
2678 gdk_color_parse("#000000", &black
);
2679 yellow_initialised
= gdk_colormap_alloc_color(
2680 gdk_colormap_get_system(), &yellow
, FALSE
, TRUE
);
2681 yellow_initialised
&= gdk_colormap_alloc_color(
2682 gdk_colormap_get_system(), &black
, FALSE
, TRUE
);
2686 for (h_list
= compose
->header_list
; h_list
!= NULL
; h_list
= h_list
->next
) {
2687 entry
= GTK_ENTRY(((ComposeHeaderEntry
*)h_list
->data
)->entry
);
2688 if (gtk_entry_get_text(entry
) &&
2689 !g_utf8_collate(gtk_entry_get_text(entry
), mailto
)) {
2690 #if !GTK_CHECK_VERSION(3, 0, 0)
2691 if (yellow_initialised
) {
2693 gtk_widget_modify_base(
2694 GTK_WIDGET(((ComposeHeaderEntry
*)h_list
->data
)->entry
),
2695 GTK_STATE_NORMAL
, &yellow
);
2696 gtk_widget_modify_text(
2697 GTK_WIDGET(((ComposeHeaderEntry
*)h_list
->data
)->entry
),
2698 GTK_STATE_NORMAL
, &black
);
2699 #if !GTK_CHECK_VERSION(3, 0, 0)
2706 void compose_toolbar_cb(gint action
, gpointer data
)
2708 ToolbarItem
*toolbar_item
= (ToolbarItem
*)data
;
2709 Compose
*compose
= (Compose
*)toolbar_item
->parent
;
2711 cm_return_if_fail(compose
!= NULL
);
2715 compose_send_cb(NULL
, compose
);
2718 compose_send_later_cb(NULL
, compose
);
2721 compose_draft(compose
, COMPOSE_QUIT_EDITING
);
2724 compose_insert_file_cb(NULL
, compose
);
2727 compose_attach_cb(NULL
, compose
);
2730 compose_insert_sig(compose
, FALSE
);
2733 compose_insert_sig(compose
, TRUE
);
2736 compose_ext_editor_cb(NULL
, compose
);
2738 case A_LINEWRAP_CURRENT
:
2739 compose_beautify_paragraph(compose
, NULL
, TRUE
);
2741 case A_LINEWRAP_ALL
:
2742 compose_wrap_all_full(compose
, TRUE
);
2745 compose_address_cb(NULL
, compose
);
2748 case A_CHECK_SPELLING
:
2749 compose_check_all(NULL
, compose
);
2757 static MailField
compose_entries_set(Compose
*compose
, const gchar
*mailto
, ComposeEntryType to_type
)
2762 gchar
*subject
= NULL
;
2766 gchar
**attach
= NULL
;
2767 gchar
*inreplyto
= NULL
;
2768 MailField mfield
= NO_FIELD_PRESENT
;
2770 /* get mailto parts but skip from */
2771 scan_mailto_url(mailto
, NULL
, &to
, &cc
, &bcc
, &subject
, &body
, &attach
, &inreplyto
);
2774 compose_entry_append(compose
, to
, to_type
, PREF_MAILTO
);
2775 mfield
= TO_FIELD_PRESENT
;
2778 compose_entry_append(compose
, cc
, COMPOSE_CC
, PREF_MAILTO
);
2780 compose_entry_append(compose
, bcc
, COMPOSE_BCC
, PREF_MAILTO
);
2782 if (!g_utf8_validate (subject
, -1, NULL
)) {
2783 temp
= g_locale_to_utf8 (subject
, -1, NULL
, &len
, NULL
);
2784 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), temp
);
2787 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), subject
);
2789 mfield
= SUBJECT_FIELD_PRESENT
;
2792 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
2793 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
2796 gboolean prev_autowrap
= compose
->autowrap
;
2798 compose
->autowrap
= FALSE
;
2800 mark
= gtk_text_buffer_get_insert(buffer
);
2801 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
2803 if (!g_utf8_validate (body
, -1, NULL
)) {
2804 temp
= g_locale_to_utf8 (body
, -1, NULL
, &len
, NULL
);
2805 gtk_text_buffer_insert(buffer
, &iter
, temp
, -1);
2808 gtk_text_buffer_insert(buffer
, &iter
, body
, -1);
2810 gtk_text_buffer_insert(buffer
, &iter
, "\n", 1);
2812 compose
->autowrap
= prev_autowrap
;
2813 if (compose
->autowrap
)
2814 compose_wrap_all(compose
);
2815 mfield
= BODY_FIELD_PRESENT
;
2819 gint i
= 0, att
= 0;
2820 gchar
*warn_files
= NULL
;
2821 while (attach
[i
] != NULL
) {
2822 gchar
*utf8_filename
= conv_filename_to_utf8(attach
[i
]);
2823 if (utf8_filename
) {
2824 if (compose_attach_append(compose
, attach
[i
], utf8_filename
, NULL
, NULL
)) {
2825 gchar
*tmp
= g_strdup_printf("%s%s\n",
2826 warn_files
?warn_files
:"",
2832 g_free(utf8_filename
);
2834 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2839 alertpanel_notice(ngettext(
2840 "The following file has been attached: \n%s",
2841 "The following files have been attached: \n%s", att
), warn_files
);
2846 compose_entry_append(compose
, inreplyto
, COMPOSE_INREPLYTO
, PREF_MAILTO
);
2859 static gint
compose_parse_header(Compose
*compose
, MsgInfo
*msginfo
)
2861 static HeaderEntry hentry
[] = {{"Reply-To:", NULL
, TRUE
},
2862 {"Cc:", NULL
, TRUE
},
2863 {"References:", NULL
, FALSE
},
2864 {"Bcc:", NULL
, TRUE
},
2865 {"Newsgroups:", NULL
, TRUE
},
2866 {"Followup-To:", NULL
, TRUE
},
2867 {"List-Post:", NULL
, FALSE
},
2868 {"X-Priority:", NULL
, FALSE
},
2869 {NULL
, NULL
, FALSE
}};
2885 cm_return_val_if_fail(msginfo
!= NULL
, -1);
2887 if ((fp
= procmsg_open_message(msginfo
)) == NULL
) return -1;
2888 procheader_get_header_fields(fp
, hentry
);
2891 if (hentry
[H_REPLY_TO
].body
!= NULL
) {
2892 if (hentry
[H_REPLY_TO
].body
[0] != '\0') {
2894 conv_unmime_header(hentry
[H_REPLY_TO
].body
,
2897 g_free(hentry
[H_REPLY_TO
].body
);
2898 hentry
[H_REPLY_TO
].body
= NULL
;
2900 if (hentry
[H_CC
].body
!= NULL
) {
2901 compose
->cc
= conv_unmime_header(hentry
[H_CC
].body
, NULL
, TRUE
);
2902 g_free(hentry
[H_CC
].body
);
2903 hentry
[H_CC
].body
= NULL
;
2905 if (hentry
[H_REFERENCES
].body
!= NULL
) {
2906 if (compose
->mode
== COMPOSE_REEDIT
)
2907 compose
->references
= hentry
[H_REFERENCES
].body
;
2909 compose
->references
= compose_parse_references
2910 (hentry
[H_REFERENCES
].body
, msginfo
->msgid
);
2911 g_free(hentry
[H_REFERENCES
].body
);
2913 hentry
[H_REFERENCES
].body
= NULL
;
2915 if (hentry
[H_BCC
].body
!= NULL
) {
2916 if (compose
->mode
== COMPOSE_REEDIT
)
2918 conv_unmime_header(hentry
[H_BCC
].body
, NULL
, TRUE
);
2919 g_free(hentry
[H_BCC
].body
);
2920 hentry
[H_BCC
].body
= NULL
;
2922 if (hentry
[H_NEWSGROUPS
].body
!= NULL
) {
2923 compose
->newsgroups
= hentry
[H_NEWSGROUPS
].body
;
2924 hentry
[H_NEWSGROUPS
].body
= NULL
;
2926 if (hentry
[H_FOLLOWUP_TO
].body
!= NULL
) {
2927 if (hentry
[H_FOLLOWUP_TO
].body
[0] != '\0') {
2928 compose
->followup_to
=
2929 conv_unmime_header(hentry
[H_FOLLOWUP_TO
].body
,
2932 g_free(hentry
[H_FOLLOWUP_TO
].body
);
2933 hentry
[H_FOLLOWUP_TO
].body
= NULL
;
2935 if (hentry
[H_LIST_POST
].body
!= NULL
) {
2936 gchar
*to
= NULL
, *start
= NULL
;
2938 extract_address(hentry
[H_LIST_POST
].body
);
2939 if (hentry
[H_LIST_POST
].body
[0] != '\0') {
2940 start
= strstr(hentry
[H_LIST_POST
].body
, "mailto:");
2942 scan_mailto_url(start
? start
: hentry
[H_LIST_POST
].body
,
2943 NULL
, &to
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
);
2946 g_free(compose
->ml_post
);
2947 compose
->ml_post
= to
;
2950 g_free(hentry
[H_LIST_POST
].body
);
2951 hentry
[H_LIST_POST
].body
= NULL
;
2954 /* CLAWS - X-Priority */
2955 if (compose
->mode
== COMPOSE_REEDIT
)
2956 if (hentry
[H_X_PRIORITY
].body
!= NULL
) {
2959 priority
= atoi(hentry
[H_X_PRIORITY
].body
);
2960 g_free(hentry
[H_X_PRIORITY
].body
);
2962 hentry
[H_X_PRIORITY
].body
= NULL
;
2964 if (priority
< PRIORITY_HIGHEST
||
2965 priority
> PRIORITY_LOWEST
)
2966 priority
= PRIORITY_NORMAL
;
2968 compose
->priority
= priority
;
2971 if (compose
->mode
== COMPOSE_REEDIT
) {
2972 if (msginfo
->inreplyto
&& *msginfo
->inreplyto
)
2973 compose
->inreplyto
= g_strdup(msginfo
->inreplyto
);
2977 if (msginfo
->msgid
&& *msginfo
->msgid
)
2978 compose
->inreplyto
= g_strdup(msginfo
->msgid
);
2980 if (!compose
->references
) {
2981 if (msginfo
->msgid
&& *msginfo
->msgid
) {
2982 if (msginfo
->inreplyto
&& *msginfo
->inreplyto
)
2983 compose
->references
=
2984 g_strdup_printf("<%s>\n\t<%s>",
2988 compose
->references
=
2989 g_strconcat("<", msginfo
->msgid
, ">",
2991 } else if (msginfo
->inreplyto
&& *msginfo
->inreplyto
) {
2992 compose
->references
=
2993 g_strconcat("<", msginfo
->inreplyto
, ">",
3001 static gint
compose_parse_manual_headers(Compose
*compose
, MsgInfo
*msginfo
, HeaderEntry
*entries
)
3006 cm_return_val_if_fail(msginfo
!= NULL
, -1);
3008 if ((fp
= procmsg_open_message(msginfo
)) == NULL
) return -1;
3009 procheader_get_header_fields(fp
, entries
);
3013 while (he
!= NULL
&& he
->name
!= NULL
) {
3015 GtkListStore
*model
= NULL
;
3017 debug_print("Adding manual header: %s with value %s\n", he
->name
, he
->body
);
3018 model
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose
->header_last
->combo
)));
3019 COMBOBOX_ADD(model
, he
->name
, COMPOSE_TO
);
3020 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose
->header_last
->combo
), &iter
);
3021 gtk_entry_set_text(GTK_ENTRY(compose
->header_last
->entry
), he
->body
);
3028 static gchar
*compose_parse_references(const gchar
*ref
, const gchar
*msgid
)
3030 GSList
*ref_id_list
, *cur
;
3034 ref_id_list
= references_list_append(NULL
, ref
);
3035 if (!ref_id_list
) return NULL
;
3036 if (msgid
&& *msgid
)
3037 ref_id_list
= g_slist_append(ref_id_list
, g_strdup(msgid
));
3042 for (cur
= ref_id_list
; cur
!= NULL
; cur
= cur
->next
)
3043 /* "<" + Message-ID + ">" + CR+LF+TAB */
3044 len
+= strlen((gchar
*)cur
->data
) + 5;
3046 if (len
> MAX_REFERENCES_LEN
) {
3047 /* remove second message-ID */
3048 if (ref_id_list
&& ref_id_list
->next
&&
3049 ref_id_list
->next
->next
) {
3050 g_free(ref_id_list
->next
->data
);
3051 ref_id_list
= g_slist_remove
3052 (ref_id_list
, ref_id_list
->next
->data
);
3054 slist_free_strings_full(ref_id_list
);
3061 new_ref
= g_string_new("");
3062 for (cur
= ref_id_list
; cur
!= NULL
; cur
= cur
->next
) {
3063 if (new_ref
->len
> 0)
3064 g_string_append(new_ref
, "\n\t");
3065 g_string_append_printf(new_ref
, "<%s>", (gchar
*)cur
->data
);
3068 slist_free_strings_full(ref_id_list
);
3070 new_ref_str
= new_ref
->str
;
3071 g_string_free(new_ref
, FALSE
);
3076 static gchar
*compose_quote_fmt(Compose
*compose
, MsgInfo
*msginfo
,
3077 const gchar
*fmt
, const gchar
*qmark
,
3078 const gchar
*body
, gboolean rewrap
,
3079 gboolean need_unescape
,
3080 const gchar
*err_msg
)
3082 MsgInfo
* dummyinfo
= NULL
;
3083 gchar
*quote_str
= NULL
;
3085 gboolean prev_autowrap
;
3086 const gchar
*trimmed_body
= body
;
3087 gint cursor_pos
= -1;
3088 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
3089 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
3094 SIGNAL_BLOCK(buffer
);
3097 dummyinfo
= compose_msginfo_new_from_compose(compose
);
3098 msginfo
= dummyinfo
;
3101 if (qmark
!= NULL
) {
3103 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
3104 compose
->gtkaspell
);
3106 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
3108 quote_fmt_scan_string(qmark
);
3111 buf
= quote_fmt_get_buffer();
3113 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3115 Xstrdup_a(quote_str
, buf
, goto error
)
3118 if (fmt
&& *fmt
!= '\0') {
3121 while (*trimmed_body
== '\n')
3125 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
, FALSE
,
3126 compose
->gtkaspell
);
3128 quote_fmt_init(msginfo
, quote_str
, trimmed_body
, FALSE
, compose
->account
, FALSE
);
3130 if (need_unescape
) {
3133 /* decode \-escape sequences in the internal representation of the quote format */
3134 tmp
= g_malloc(strlen(fmt
)+1);
3135 pref_get_unescaped_pref(tmp
, fmt
);
3136 quote_fmt_scan_string(tmp
);
3140 quote_fmt_scan_string(fmt
);
3144 buf
= quote_fmt_get_buffer();
3146 gint line
= quote_fmt_get_line();
3147 alertpanel_error(err_msg
, line
);
3153 prev_autowrap
= compose
->autowrap
;
3154 compose
->autowrap
= FALSE
;
3156 mark
= gtk_text_buffer_get_insert(buffer
);
3157 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3158 if (g_utf8_validate(buf
, -1, NULL
)) {
3159 gtk_text_buffer_insert(buffer
, &iter
, buf
, -1);
3161 gchar
*tmpout
= NULL
;
3162 tmpout
= conv_codeset_strdup
3163 (buf
, conv_get_locale_charset_str_no_utf8(),
3165 if (!tmpout
|| !g_utf8_validate(tmpout
, -1, NULL
)) {
3167 tmpout
= g_malloc(strlen(buf
)*2+1);
3168 conv_localetodisp(tmpout
, strlen(buf
)*2+1, buf
);
3170 gtk_text_buffer_insert(buffer
, &iter
, tmpout
, -1);
3174 cursor_pos
= quote_fmt_get_cursor_pos();
3175 if (cursor_pos
== -1)
3176 cursor_pos
= gtk_text_iter_get_offset(&iter
);
3177 compose
->set_cursor_pos
= cursor_pos
;
3179 gtk_text_buffer_get_start_iter(buffer
, &iter
);
3180 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cursor_pos
);
3181 gtk_text_buffer_place_cursor(buffer
, &iter
);
3183 compose
->autowrap
= prev_autowrap
;
3184 if (compose
->autowrap
&& rewrap
)
3185 compose_wrap_all(compose
);
3192 SIGNAL_UNBLOCK(buffer
);
3194 procmsg_msginfo_free( &dummyinfo
);
3199 /* if ml_post is of type addr@host and from is of type
3200 * addr-anything@host, return TRUE
3202 static gboolean
is_subscription(const gchar
*ml_post
, const gchar
*from
)
3204 gchar
*left_ml
= NULL
;
3205 gchar
*right_ml
= NULL
;
3206 gchar
*left_from
= NULL
;
3207 gchar
*right_from
= NULL
;
3208 gboolean result
= FALSE
;
3210 if (!ml_post
|| !from
)
3213 left_ml
= g_strdup(ml_post
);
3214 if (strstr(left_ml
, "@")) {
3215 right_ml
= strstr(left_ml
, "@")+1;
3216 *(strstr(left_ml
, "@")) = '\0';
3219 left_from
= g_strdup(from
);
3220 if (strstr(left_from
, "@")) {
3221 right_from
= strstr(left_from
, "@")+1;
3222 *(strstr(left_from
, "@")) = '\0';
3225 if (right_ml
&& right_from
3226 && !strncmp(left_from
, left_ml
, strlen(left_ml
))
3227 && !strcmp(right_from
, right_ml
)) {
3236 static void compose_set_folder_prefs(Compose
*compose
, FolderItem
*folder
,
3237 gboolean respect_default_to
)
3241 if (!folder
|| !folder
->prefs
)
3244 if (respect_default_to
&& folder
->prefs
->enable_default_to
) {
3245 compose_entry_append(compose
, folder
->prefs
->default_to
,
3246 COMPOSE_TO
, PREF_FOLDER
);
3247 compose_entry_mark_default_to(compose
, folder
->prefs
->default_to
);
3249 if (folder
->prefs
->enable_default_cc
)
3250 compose_entry_append(compose
, folder
->prefs
->default_cc
,
3251 COMPOSE_CC
, PREF_FOLDER
);
3252 if (folder
->prefs
->enable_default_bcc
)
3253 compose_entry_append(compose
, folder
->prefs
->default_bcc
,
3254 COMPOSE_BCC
, PREF_FOLDER
);
3255 if (folder
->prefs
->enable_default_replyto
)
3256 compose_entry_append(compose
, folder
->prefs
->default_replyto
,
3257 COMPOSE_REPLYTO
, PREF_FOLDER
);
3260 static void compose_reply_set_subject(Compose
*compose
, MsgInfo
*msginfo
)
3265 if (!compose
|| !msginfo
)
3268 if (msginfo
->subject
&& *msginfo
->subject
) {
3269 buf
= p
= g_strdup(msginfo
->subject
);
3270 p
+= subject_get_prefix_length(p
);
3271 memmove(buf
, p
, strlen(p
) + 1);
3273 buf2
= g_strdup_printf("Re: %s", buf
);
3274 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf2
);
3279 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), "Re: ");
3282 static void compose_reply_set_entry(Compose
*compose
, MsgInfo
*msginfo
,
3283 gboolean to_all
, gboolean to_ml
,
3285 gboolean followup_and_reply_to
)
3287 GSList
*cc_list
= NULL
;
3290 gchar
*replyto
= NULL
;
3291 gchar
*ac_email
= NULL
;
3293 gboolean reply_to_ml
= FALSE
;
3294 gboolean default_reply_to
= FALSE
;
3296 cm_return_if_fail(compose
->account
!= NULL
);
3297 cm_return_if_fail(msginfo
!= NULL
);
3299 reply_to_ml
= to_ml
&& compose
->ml_post
;
3301 default_reply_to
= msginfo
->folder
&&
3302 msginfo
->folder
->prefs
->enable_default_reply_to
;
3304 if (compose
->account
->protocol
!= A_NNTP
) {
3305 compose_set_folder_prefs(compose
, msginfo
->folder
, FALSE
);
3307 if (reply_to_ml
&& !default_reply_to
) {
3309 gboolean is_subscr
= is_subscription(compose
->ml_post
,
3312 /* normal answer to ml post with a reply-to */
3313 compose_entry_append(compose
,
3315 COMPOSE_TO
, PREF_ML
);
3316 if (compose
->replyto
)
3317 compose_entry_append(compose
,
3319 COMPOSE_CC
, PREF_ML
);
3321 /* answer to subscription confirmation */
3322 if (compose
->replyto
)
3323 compose_entry_append(compose
,
3325 COMPOSE_TO
, PREF_ML
);
3326 else if (msginfo
->from
)
3327 compose_entry_append(compose
,
3329 COMPOSE_TO
, PREF_ML
);
3332 else if (!(to_all
|| to_sender
) && default_reply_to
) {
3333 compose_entry_append(compose
,
3334 msginfo
->folder
->prefs
->default_reply_to
,
3335 COMPOSE_TO
, PREF_FOLDER
);
3336 compose_entry_mark_default_to(compose
,
3337 msginfo
->folder
->prefs
->default_reply_to
);
3343 compose_entry_append(compose
, msginfo
->from
,
3344 COMPOSE_TO
, PREF_NONE
);
3346 Xstrdup_a(tmp1
, msginfo
->from
, return);
3347 extract_address(tmp1
);
3348 compose_entry_append(compose
,
3349 (!account_find_from_address(tmp1
, FALSE
))
3352 COMPOSE_TO
, PREF_NONE
);
3353 if (compose
->replyto
)
3354 compose_entry_append(compose
,
3356 COMPOSE_CC
, PREF_NONE
);
3358 if (!folder_has_parent_of_type(msginfo
->folder
, F_QUEUE
) &&
3359 !folder_has_parent_of_type(msginfo
->folder
, F_OUTBOX
) &&
3360 !folder_has_parent_of_type(msginfo
->folder
, F_DRAFT
)) {
3361 if (compose
->replyto
) {
3362 compose_entry_append(compose
,
3364 COMPOSE_TO
, PREF_NONE
);
3366 compose_entry_append(compose
,
3367 msginfo
->from
? msginfo
->from
: "",
3368 COMPOSE_TO
, PREF_NONE
);
3371 /* replying to own mail, use original recp */
3372 compose_entry_append(compose
,
3373 msginfo
->to
? msginfo
->to
: "",
3374 COMPOSE_TO
, PREF_NONE
);
3375 compose_entry_append(compose
,
3376 msginfo
->cc
? msginfo
->cc
: "",
3377 COMPOSE_CC
, PREF_NONE
);
3382 if (to_sender
|| (compose
->followup_to
&&
3383 !strncmp(compose
->followup_to
, "poster", 6)))
3384 compose_entry_append
3386 (compose
->replyto
? compose
->replyto
:
3387 msginfo
->from
? msginfo
->from
: ""),
3388 COMPOSE_TO
, PREF_NONE
);
3390 else if (followup_and_reply_to
|| to_all
) {
3391 compose_entry_append
3393 (compose
->replyto
? compose
->replyto
:
3394 msginfo
->from
? msginfo
->from
: ""),
3395 COMPOSE_TO
, PREF_NONE
);
3397 compose_entry_append
3399 compose
->followup_to
? compose
->followup_to
:
3400 compose
->newsgroups
? compose
->newsgroups
: "",
3401 COMPOSE_NEWSGROUPS
, PREF_NONE
);
3403 compose_entry_append
3405 msginfo
->cc
? msginfo
->cc
: "",
3406 COMPOSE_CC
, PREF_NONE
);
3409 compose_entry_append
3411 compose
->followup_to
? compose
->followup_to
:
3412 compose
->newsgroups
? compose
->newsgroups
: "",
3413 COMPOSE_NEWSGROUPS
, PREF_NONE
);
3415 compose_reply_set_subject(compose
, msginfo
);
3417 if (to_ml
&& compose
->ml_post
) return;
3418 if (!to_all
|| compose
->account
->protocol
== A_NNTP
) return;
3420 if (compose
->replyto
) {
3421 Xstrdup_a(replyto
, compose
->replyto
, return);
3422 extract_address(replyto
);
3424 if (msginfo
->from
) {
3425 Xstrdup_a(from
, msginfo
->from
, return);
3426 extract_address(from
);
3429 if (replyto
&& from
)
3430 cc_list
= address_list_append_with_comments(cc_list
, from
);
3431 if (to_all
&& msginfo
->folder
&&
3432 msginfo
->folder
->prefs
->enable_default_reply_to
)
3433 cc_list
= address_list_append_with_comments(cc_list
,
3434 msginfo
->folder
->prefs
->default_reply_to
);
3435 cc_list
= address_list_append_with_comments(cc_list
, msginfo
->to
);
3436 cc_list
= address_list_append_with_comments(cc_list
, compose
->cc
);
3438 ac_email
= g_utf8_strdown(compose
->account
->address
, -1);
3441 for (cur
= cc_list
; cur
!= NULL
; cur
= cur
->next
) {
3442 gchar
*addr
= g_utf8_strdown(cur
->data
, -1);
3443 extract_address(addr
);
3445 if (strcmp(ac_email
, addr
))
3446 compose_entry_append(compose
, (gchar
*)cur
->data
,
3447 COMPOSE_CC
, PREF_NONE
);
3449 debug_print("Cc address same as compose account's, ignoring\n");
3454 slist_free_strings_full(cc_list
);
3460 #define SET_ENTRY(entry, str) \
3463 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3466 #define SET_ADDRESS(type, str) \
3469 compose_entry_append(compose, str, type, PREF_NONE); \
3472 static void compose_reedit_set_entry(Compose
*compose
, MsgInfo
*msginfo
)
3474 cm_return_if_fail(msginfo
!= NULL
);
3476 SET_ENTRY(subject_entry
, msginfo
->subject
);
3477 SET_ENTRY(from_name
, msginfo
->from
);
3478 SET_ADDRESS(COMPOSE_TO
, msginfo
->to
);
3479 SET_ADDRESS(COMPOSE_CC
, compose
->cc
);
3480 SET_ADDRESS(COMPOSE_BCC
, compose
->bcc
);
3481 SET_ADDRESS(COMPOSE_REPLYTO
, compose
->replyto
);
3482 SET_ADDRESS(COMPOSE_NEWSGROUPS
, compose
->newsgroups
);
3483 SET_ADDRESS(COMPOSE_FOLLOWUPTO
, compose
->followup_to
);
3485 compose_update_priority_menu_item(compose
);
3486 compose_update_privacy_system_menu_item(compose
, FALSE
);
3487 compose_show_first_last_header(compose
, TRUE
);
3493 static void compose_insert_sig(Compose
*compose
, gboolean replace
)
3495 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
3496 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
3498 GtkTextIter iter
, iter_end
;
3499 gint cur_pos
, ins_pos
;
3500 gboolean prev_autowrap
;
3501 gboolean found
= FALSE
;
3502 gboolean exists
= FALSE
;
3504 cm_return_if_fail(compose
->account
!= NULL
);
3508 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
3509 G_CALLBACK(compose_changed_cb
),
3512 mark
= gtk_text_buffer_get_insert(buffer
);
3513 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3514 cur_pos
= gtk_text_iter_get_offset (&iter
);
3517 gtk_text_buffer_get_end_iter(buffer
, &iter
);
3519 exists
= (compose
->sig_str
!= NULL
);
3522 GtkTextIter first_iter
, start_iter
, end_iter
;
3524 gtk_text_buffer_get_start_iter(buffer
, &first_iter
);
3526 if (!exists
|| compose
->sig_str
[0] == '\0')
3529 found
= gtk_text_iter_forward_to_tag_toggle(&first_iter
,
3530 compose
->signature_tag
);
3533 /* include previous \n\n */
3534 gtk_text_iter_backward_chars(&first_iter
, 1);
3535 start_iter
= first_iter
;
3536 end_iter
= first_iter
;
3538 found
= gtk_text_iter_forward_to_tag_toggle(&end_iter
,
3539 compose
->signature_tag
);
3540 found
&= gtk_text_iter_forward_to_tag_toggle(&end_iter
,
3541 compose
->signature_tag
);
3543 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
3549 g_free(compose
->sig_str
);
3550 compose
->sig_str
= account_get_signature_str(compose
->account
);
3552 cur_pos
= gtk_text_iter_get_offset(&iter
);
3554 if (!compose
->sig_str
|| (replace
&& !compose
->account
->auto_sig
)) {
3555 g_free(compose
->sig_str
);
3556 compose
->sig_str
= NULL
;
3558 if (compose
->sig_inserted
== FALSE
)
3559 gtk_text_buffer_insert(buffer
, &iter
, "\n", -1);
3560 compose
->sig_inserted
= TRUE
;
3562 cur_pos
= gtk_text_iter_get_offset(&iter
);
3563 gtk_text_buffer_insert(buffer
, &iter
, compose
->sig_str
, -1);
3565 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cur_pos
);
3566 gtk_text_iter_forward_chars(&iter
, 1);
3567 gtk_text_buffer_get_end_iter(buffer
, &iter_end
);
3568 gtk_text_buffer_apply_tag_by_name(buffer
,"signature",&iter
, &iter_end
);
3570 if (cur_pos
> gtk_text_buffer_get_char_count (buffer
))
3571 cur_pos
= gtk_text_buffer_get_char_count (buffer
);
3574 /* put the cursor where it should be
3575 * either where the quote_fmt says, either where it was */
3576 if (compose
->set_cursor_pos
< 0)
3577 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, ins_pos
);
3579 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
,
3580 compose
->set_cursor_pos
);
3582 compose
->set_cursor_pos
= -1;
3583 gtk_text_buffer_place_cursor(buffer
, &iter
);
3584 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3585 G_CALLBACK(compose_changed_cb
),
3591 static ComposeInsertResult
compose_insert_file(Compose
*compose
, const gchar
*file
)
3594 GtkTextBuffer
*buffer
;
3597 const gchar
*cur_encoding
;
3598 gchar buf
[BUFFSIZE
];
3601 gboolean prev_autowrap
;
3604 GString
*file_contents
= NULL
;
3605 ComposeInsertResult result
= COMPOSE_INSERT_SUCCESS
;
3607 cm_return_val_if_fail(file
!= NULL
, COMPOSE_INSERT_NO_FILE
);
3609 /* get the size of the file we are about to insert */
3610 ret
= g_stat(file
, &file_stat
);
3612 gchar
*shortfile
= g_path_get_basename(file
);
3613 alertpanel_error(_("Could not get size of file '%s'."), shortfile
);
3615 return COMPOSE_INSERT_NO_FILE
;
3616 } else if (prefs_common
.warn_large_insert
== TRUE
) {
3618 /* ask user for confirmation if the file is large */
3619 if (prefs_common
.warn_large_insert_size
< 0 ||
3620 file_stat
.st_size
> (prefs_common
.warn_large_insert_size
* 1024)) {
3624 msg
= g_strdup_printf(_("You are about to insert a file of %s "
3625 "in the message body. Are you sure you want to do that?"),
3626 to_human_readable(file_stat
.st_size
));
3627 aval
= alertpanel_full(_("Are you sure?"), msg
, GTK_STOCK_CANCEL
,
3628 g_strconcat("+", _("_Insert"), NULL
), NULL
, TRUE
, NULL
, ALERT_QUESTION
, G_ALERTDEFAULT
);
3631 /* do we ask for confirmation next time? */
3632 if (aval
& G_ALERTDISABLE
) {
3633 /* no confirmation next time, disable feature in preferences */
3634 aval
&= ~G_ALERTDISABLE
;
3635 prefs_common
.warn_large_insert
= FALSE
;
3638 /* abort file insertion if user canceled action */
3639 if (aval
!= G_ALERTALTERNATE
) {
3640 return COMPOSE_INSERT_NO_FILE
;
3646 if ((fp
= g_fopen(file
, "rb")) == NULL
) {
3647 FILE_OP_ERROR(file
, "fopen");
3648 return COMPOSE_INSERT_READ_ERROR
;
3651 prev_autowrap
= compose
->autowrap
;
3652 compose
->autowrap
= FALSE
;
3654 text
= GTK_TEXT_VIEW(compose
->text
);
3655 buffer
= gtk_text_view_get_buffer(text
);
3656 mark
= gtk_text_buffer_get_insert(buffer
);
3657 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
3659 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
3660 G_CALLBACK(text_inserted
),
3663 cur_encoding
= conv_get_locale_charset_str_no_utf8();
3665 file_contents
= g_string_new("");
3666 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
3669 if (g_utf8_validate(buf
, -1, NULL
) == TRUE
)
3670 str
= g_strdup(buf
);
3672 codeconv_set_strict(TRUE
);
3673 str
= conv_codeset_strdup
3674 (buf
, cur_encoding
, CS_INTERNAL
);
3675 codeconv_set_strict(FALSE
);
3678 result
= COMPOSE_INSERT_INVALID_CHARACTER
;
3684 /* strip <CR> if DOS/Windows file,
3685 replace <CR> with <LF> if Macintosh file. */
3688 if (len
> 0 && str
[len
- 1] != '\n') {
3690 if (str
[len
] == '\r') str
[len
] = '\n';
3693 file_contents
= g_string_append(file_contents
, str
);
3697 if (result
== COMPOSE_INSERT_SUCCESS
) {
3698 gtk_text_buffer_insert(buffer
, &iter
, file_contents
->str
, -1);
3700 compose_changed_cb(NULL
, compose
);
3701 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
3702 G_CALLBACK(text_inserted
),
3704 compose
->autowrap
= prev_autowrap
;
3705 if (compose
->autowrap
)
3706 compose_wrap_all(compose
);
3709 g_string_free(file_contents
, TRUE
);
3715 static gboolean
compose_attach_append(Compose
*compose
, const gchar
*file
,
3716 const gchar
*filename
,
3717 const gchar
*content_type
,
3718 const gchar
*charset
)
3726 GtkListStore
*store
;
3728 gboolean has_binary
= FALSE
;
3730 if (!is_file_exist(file
)) {
3731 gchar
*file_from_uri
= g_filename_from_uri(file
, NULL
, NULL
);
3732 gboolean result
= FALSE
;
3733 if (file_from_uri
&& is_file_exist(file_from_uri
)) {
3734 result
= compose_attach_append(
3735 compose
, file_from_uri
,
3736 filename
, content_type
,
3739 g_free(file_from_uri
);
3742 alertpanel_error("File %s doesn't exist\n", filename
);
3745 if ((size
= get_file_size(file
)) < 0) {
3746 alertpanel_error("Can't get file size of %s\n", filename
);
3750 /* In batch mode, we allow 0-length files to be attached no questions asked */
3751 if (size
== 0 && !compose
->batch
) {
3752 gchar
* msg
= g_strdup_printf(_("File %s is empty."), filename
);
3753 AlertValue aval
= alertpanel_full(_("Empty file"), msg
,
3754 GTK_STOCK_CANCEL
, g_strconcat("+", _("_Attach anyway"), NULL
), NULL
, FALSE
,
3755 NULL
, ALERT_WARNING
, G_ALERTDEFAULT
);
3758 if (aval
!= G_ALERTALTERNATE
) {
3762 if ((fp
= g_fopen(file
, "rb")) == NULL
) {
3763 alertpanel_error(_("Can't read %s."), filename
);
3768 ainfo
= g_new0(AttachInfo
, 1);
3769 auto_ainfo
= g_auto_pointer_new_with_free
3770 (ainfo
, (GFreeFunc
) compose_attach_info_free
);
3771 ainfo
->file
= g_strdup(file
);
3774 ainfo
->content_type
= g_strdup(content_type
);
3775 if (!g_ascii_strcasecmp(content_type
, "message/rfc822")) {
3777 MsgFlags flags
= {0, 0};
3779 if (procmime_get_encoding_for_text_file(file
, &has_binary
) == ENC_7BIT
)
3780 ainfo
->encoding
= ENC_7BIT
;
3782 ainfo
->encoding
= ENC_8BIT
;
3784 msginfo
= procheader_parse_file(file
, flags
, FALSE
, FALSE
);
3785 if (msginfo
&& msginfo
->subject
)
3786 name
= g_strdup(msginfo
->subject
);
3788 name
= g_path_get_basename(filename
? filename
: file
);
3790 ainfo
->name
= g_strdup_printf(_("Message: %s"), name
);
3792 procmsg_msginfo_free(&msginfo
);
3794 if (!g_ascii_strncasecmp(content_type
, "text/", 5)) {
3795 ainfo
->charset
= g_strdup(charset
);
3796 ainfo
->encoding
= procmime_get_encoding_for_text_file(file
, &has_binary
);
3798 ainfo
->encoding
= ENC_BASE64
;
3800 name
= g_path_get_basename(filename
? filename
: file
);
3801 ainfo
->name
= g_strdup(name
);
3805 ainfo
->content_type
= procmime_get_mime_type(file
);
3806 if (!ainfo
->content_type
) {
3807 ainfo
->content_type
=
3808 g_strdup("application/octet-stream");
3809 ainfo
->encoding
= ENC_BASE64
;
3810 } else if (!g_ascii_strncasecmp(ainfo
->content_type
, "text/", 5))
3812 procmime_get_encoding_for_text_file(file
, &has_binary
);
3814 ainfo
->encoding
= ENC_BASE64
;
3815 name
= g_path_get_basename(filename
? filename
: file
);
3816 ainfo
->name
= g_strdup(name
);
3820 if (ainfo
->name
!= NULL
3821 && !strcmp(ainfo
->name
, ".")) {
3822 g_free(ainfo
->name
);
3826 if (!strcmp(ainfo
->content_type
, "unknown") || has_binary
) {
3827 g_free(ainfo
->content_type
);
3828 ainfo
->content_type
= g_strdup("application/octet-stream");
3829 g_free(ainfo
->charset
);
3830 ainfo
->charset
= NULL
;
3833 ainfo
->size
= (goffset
)size
;
3834 size_text
= to_human_readable((goffset
)size
);
3836 store
= GTK_LIST_STORE(gtk_tree_view_get_model
3837 (GTK_TREE_VIEW(compose
->attach_clist
)));
3839 gtk_list_store_append(store
, &iter
);
3840 gtk_list_store_set(store
, &iter
,
3841 COL_MIMETYPE
, ainfo
->content_type
,
3842 COL_SIZE
, size_text
,
3843 COL_NAME
, ainfo
->name
,
3844 COL_CHARSET
, ainfo
->charset
,
3846 COL_AUTODATA
, auto_ainfo
,
3849 g_auto_pointer_free(auto_ainfo
);
3850 compose_attach_update_label(compose
);
3854 static void compose_use_signing(Compose
*compose
, gboolean use_signing
)
3856 compose
->use_signing
= use_signing
;
3857 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", use_signing
);
3860 static void compose_use_encryption(Compose
*compose
, gboolean use_encryption
)
3862 compose
->use_encryption
= use_encryption
;
3863 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", use_encryption
);
3866 #define NEXT_PART_NOT_CHILD(info) \
3868 node = info->node; \
3869 while (node->children) \
3870 node = g_node_last_child(node); \
3871 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3874 static void compose_attach_parts(Compose
*compose
, MsgInfo
*msginfo
)
3878 MimeInfo
*firsttext
= NULL
;
3879 MimeInfo
*encrypted
= NULL
;
3882 const gchar
*partname
= NULL
;
3884 mimeinfo
= procmime_scan_message(msginfo
);
3885 if (!mimeinfo
) return;
3887 if (mimeinfo
->node
->children
== NULL
) {
3888 procmime_mimeinfo_free_all(&mimeinfo
);
3892 /* find first content part */
3893 child
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
3894 while (child
&& child
->node
->children
&& (child
->type
== MIMETYPE_MULTIPART
))
3895 child
= (MimeInfo
*)child
->node
->children
->data
;
3898 if (child
->type
== MIMETYPE_TEXT
) {
3900 debug_print("First text part found\n");
3901 } else if (compose
->mode
== COMPOSE_REEDIT
&&
3902 child
->type
== MIMETYPE_APPLICATION
&&
3903 !g_ascii_strcasecmp(child
->subtype
, "pgp-encrypted")) {
3904 encrypted
= (MimeInfo
*)child
->node
->parent
->data
;
3907 child
= (MimeInfo
*) mimeinfo
->node
->children
->data
;
3908 while (child
!= NULL
) {
3911 if (child
== encrypted
) {
3912 /* skip this part of tree */
3913 NEXT_PART_NOT_CHILD(child
);
3917 if (child
->type
== MIMETYPE_MULTIPART
) {
3918 /* get the actual content */
3919 child
= procmime_mimeinfo_next(child
);
3923 if (child
== firsttext
) {
3924 child
= procmime_mimeinfo_next(child
);
3928 outfile
= procmime_get_tmp_file_name(child
);
3929 if ((err
= procmime_get_part(outfile
, child
)) < 0)
3930 g_warning("Can't get the part of multipart message. (%s)", g_strerror(-err
));
3932 gchar
*content_type
;
3934 content_type
= procmime_get_content_type_str(child
->type
, child
->subtype
);
3936 /* if we meet a pgp signature, we don't attach it, but
3937 * we force signing. */
3938 if ((strcmp(content_type
, "application/pgp-signature") &&
3939 strcmp(content_type
, "application/pkcs7-signature") &&
3940 strcmp(content_type
, "application/x-pkcs7-signature"))
3941 || compose
->mode
== COMPOSE_REDIRECT
) {
3942 partname
= procmime_mimeinfo_get_parameter(child
, "filename");
3943 if (partname
== NULL
)
3944 partname
= procmime_mimeinfo_get_parameter(child
, "name");
3945 if (partname
== NULL
)
3947 compose_attach_append(compose
, outfile
,
3948 partname
, content_type
,
3949 procmime_mimeinfo_get_parameter(child
, "charset"));
3951 compose_force_signing(compose
, compose
->account
, NULL
);
3953 g_free(content_type
);
3956 NEXT_PART_NOT_CHILD(child
);
3958 procmime_mimeinfo_free_all(&mimeinfo
);
3961 #undef NEXT_PART_NOT_CHILD
3966 WAIT_FOR_INDENT_CHAR
,
3967 WAIT_FOR_INDENT_CHAR_OR_SPACE
,
3970 /* return indent length, we allow:
3971 indent characters followed by indent characters or spaces/tabs,
3972 alphabets and numbers immediately followed by indent characters,
3973 and the repeating sequences of the above
3974 If quote ends with multiple spaces, only the first one is included. */
3975 static gchar
*compose_get_quote_str(GtkTextBuffer
*buffer
,
3976 const GtkTextIter
*start
, gint
*len
)
3978 GtkTextIter iter
= *start
;
3982 IndentState state
= WAIT_FOR_INDENT_CHAR
;
3985 gint alnum_count
= 0;
3986 gint space_count
= 0;
3989 if (prefs_common
.quote_chars
== NULL
) {
3993 while (!gtk_text_iter_ends_line(&iter
)) {
3994 wc
= gtk_text_iter_get_char(&iter
);
3995 if (g_unichar_iswide(wc
))
3997 clen
= g_unichar_to_utf8(wc
, ch
);
4001 is_indent
= strchr(prefs_common
.quote_chars
, ch
[0]) ? TRUE
: FALSE
;
4002 is_space
= g_unichar_isspace(wc
);
4004 if (state
== WAIT_FOR_INDENT_CHAR
) {
4005 if (!is_indent
&& !g_unichar_isalnum(wc
))
4008 quote_len
+= alnum_count
+ space_count
+ 1;
4009 alnum_count
= space_count
= 0;
4010 state
= WAIT_FOR_INDENT_CHAR_OR_SPACE
;
4013 } else if (state
== WAIT_FOR_INDENT_CHAR_OR_SPACE
) {
4014 if (!is_indent
&& !is_space
&& !g_unichar_isalnum(wc
))
4018 else if (is_indent
) {
4019 quote_len
+= alnum_count
+ space_count
+ 1;
4020 alnum_count
= space_count
= 0;
4023 state
= WAIT_FOR_INDENT_CHAR
;
4027 gtk_text_iter_forward_char(&iter
);
4030 if (quote_len
> 0 && space_count
> 0)
4036 if (quote_len
> 0) {
4038 gtk_text_iter_forward_chars(&iter
, quote_len
);
4039 return gtk_text_buffer_get_text(buffer
, start
, &iter
, FALSE
);
4045 /* return >0 if the line is itemized */
4046 static int compose_itemized_length(GtkTextBuffer
*buffer
,
4047 const GtkTextIter
*start
)
4049 GtkTextIter iter
= *start
;
4054 if (gtk_text_iter_ends_line(&iter
))
4059 wc
= gtk_text_iter_get_char(&iter
);
4060 if (!g_unichar_isspace(wc
))
4062 gtk_text_iter_forward_char(&iter
);
4063 if (gtk_text_iter_ends_line(&iter
))
4067 clen
= g_unichar_to_utf8(wc
, ch
);
4071 if (!strchr("*-+", ch
[0]))
4074 gtk_text_iter_forward_char(&iter
);
4075 if (gtk_text_iter_ends_line(&iter
))
4077 wc
= gtk_text_iter_get_char(&iter
);
4078 if (g_unichar_isspace(wc
)) {
4084 /* return the string at the start of the itemization */
4085 static gchar
* compose_get_itemized_chars(GtkTextBuffer
*buffer
,
4086 const GtkTextIter
*start
)
4088 GtkTextIter iter
= *start
;
4091 GString
*item_chars
= g_string_new("");
4094 if (gtk_text_iter_ends_line(&iter
))
4099 wc
= gtk_text_iter_get_char(&iter
);
4100 if (!g_unichar_isspace(wc
))
4102 gtk_text_iter_forward_char(&iter
);
4103 if (gtk_text_iter_ends_line(&iter
))
4105 g_string_append_unichar(item_chars
, wc
);
4108 str
= item_chars
->str
;
4109 g_string_free(item_chars
, FALSE
);
4113 /* return the number of spaces at a line's start */
4114 static int compose_left_offset_length(GtkTextBuffer
*buffer
,
4115 const GtkTextIter
*start
)
4117 GtkTextIter iter
= *start
;
4120 if (gtk_text_iter_ends_line(&iter
))
4124 wc
= gtk_text_iter_get_char(&iter
);
4125 if (!g_unichar_isspace(wc
))
4128 gtk_text_iter_forward_char(&iter
);
4129 if (gtk_text_iter_ends_line(&iter
))
4133 gtk_text_iter_forward_char(&iter
);
4134 if (gtk_text_iter_ends_line(&iter
))
4139 static gboolean
compose_get_line_break_pos(GtkTextBuffer
*buffer
,
4140 const GtkTextIter
*start
,
4141 GtkTextIter
*break_pos
,
4145 GtkTextIter iter
= *start
, line_end
= *start
;
4146 PangoLogAttr
*attrs
;
4153 gboolean can_break
= FALSE
;
4154 gboolean do_break
= FALSE
;
4155 gboolean was_white
= FALSE
;
4156 gboolean prev_dont_break
= FALSE
;
4158 gtk_text_iter_forward_to_line_end(&line_end
);
4159 str
= gtk_text_buffer_get_text(buffer
, &iter
, &line_end
, FALSE
);
4160 len
= g_utf8_strlen(str
, -1);
4164 g_warning("compose_get_line_break_pos: len = 0!");
4168 /* g_print("breaking line: %d: %s (len = %d)\n",
4169 gtk_text_iter_get_line(&iter), str, len); */
4171 attrs
= g_new(PangoLogAttr
, len
+ 1);
4173 pango_default_break(str
, -1, NULL
, attrs
, len
+ 1);
4177 /* skip quote and leading spaces */
4178 for (i
= 0; *p
!= '\0' && i
< len
; i
++) {
4181 wc
= g_utf8_get_char(p
);
4182 if (i
>= quote_len
&& !g_unichar_isspace(wc
))
4184 if (g_unichar_iswide(wc
))
4186 else if (*p
== '\t')
4190 p
= g_utf8_next_char(p
);
4193 for (; *p
!= '\0' && i
< len
; i
++) {
4194 PangoLogAttr
*attr
= attrs
+ i
;
4198 if (attr
->is_line_break
&& can_break
&& was_white
&& !prev_dont_break
)
4201 was_white
= attr
->is_white
;
4203 /* don't wrap URI */
4204 if ((uri_len
= get_uri_len(p
)) > 0) {
4206 if (pos
> 0 && col
> max_col
) {
4216 wc
= g_utf8_get_char(p
);
4217 if (g_unichar_iswide(wc
)) {
4219 if (prev_dont_break
&& can_break
&& attr
->is_line_break
)
4221 } else if (*p
== '\t')
4225 if (pos
> 0 && col
> max_col
) {
4230 if (*p
== '-' || *p
== '/')
4231 prev_dont_break
= TRUE
;
4233 prev_dont_break
= FALSE
;
4235 p
= g_utf8_next_char(p
);
4239 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4244 *break_pos
= *start
;
4245 gtk_text_iter_set_line_offset(break_pos
, pos
);
4250 static gboolean
compose_join_next_line(Compose
*compose
,
4251 GtkTextBuffer
*buffer
,
4253 const gchar
*quote_str
)
4255 GtkTextIter iter_
= *iter
, cur
, prev
, next
, end
;
4256 PangoLogAttr attrs
[3];
4258 gchar
*next_quote_str
;
4261 gboolean keep_cursor
= FALSE
;
4263 if (!gtk_text_iter_forward_line(&iter_
) ||
4264 gtk_text_iter_ends_line(&iter_
)) {
4267 next_quote_str
= compose_get_quote_str(buffer
, &iter_
, "e_len
);
4269 if ((quote_str
|| next_quote_str
) &&
4270 strcmp2(quote_str
, next_quote_str
) != 0) {
4271 g_free(next_quote_str
);
4274 g_free(next_quote_str
);
4277 if (quote_len
> 0) {
4278 gtk_text_iter_forward_chars(&end
, quote_len
);
4279 if (gtk_text_iter_ends_line(&end
)) {
4284 /* don't join itemized lines */
4285 if (compose_itemized_length(buffer
, &end
) > 0) {
4289 /* don't join signature separator */
4290 if (compose_is_sig_separator(compose
, buffer
, &iter_
)) {
4293 /* delete quote str */
4295 gtk_text_buffer_delete(buffer
, &iter_
, &end
);
4297 /* don't join line breaks put by the user */
4299 gtk_text_iter_backward_char(&cur
);
4300 if (gtk_text_iter_has_tag(&cur
, compose
->no_join_tag
)) {
4301 gtk_text_iter_forward_char(&cur
);
4305 gtk_text_iter_forward_char(&cur
);
4306 /* delete linebreak and extra spaces */
4307 while (gtk_text_iter_backward_char(&cur
)) {
4308 wc1
= gtk_text_iter_get_char(&cur
);
4309 if (!g_unichar_isspace(wc1
))
4314 while (!gtk_text_iter_ends_line(&cur
)) {
4315 wc1
= gtk_text_iter_get_char(&cur
);
4316 if (!g_unichar_isspace(wc1
))
4318 gtk_text_iter_forward_char(&cur
);
4321 if (!gtk_text_iter_equal(&prev
, &next
)) {
4324 mark
= gtk_text_buffer_get_insert(buffer
);
4325 gtk_text_buffer_get_iter_at_mark(buffer
, &cur
, mark
);
4326 if (gtk_text_iter_equal(&prev
, &cur
))
4328 gtk_text_buffer_delete(buffer
, &prev
, &next
);
4332 /* insert space if required */
4333 gtk_text_iter_backward_char(&prev
);
4334 wc1
= gtk_text_iter_get_char(&prev
);
4335 wc2
= gtk_text_iter_get_char(&next
);
4336 gtk_text_iter_forward_char(&next
);
4337 str
= gtk_text_buffer_get_text(buffer
, &prev
, &next
, FALSE
);
4338 pango_default_break(str
, -1, NULL
, attrs
, 3);
4339 if (!attrs
[1].is_line_break
||
4340 (!g_unichar_iswide(wc1
) || !g_unichar_iswide(wc2
))) {
4341 gtk_text_buffer_insert(buffer
, &iter_
, " ", 1);
4343 gtk_text_iter_backward_char(&iter_
);
4344 gtk_text_buffer_place_cursor(buffer
, &iter_
);
4353 #define ADD_TXT_POS(bp_, ep_, pti_) \
4354 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4355 last = last->next; \
4356 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4357 last->next = NULL; \
4359 g_warning("alloc error scanning URIs"); \
4362 static gboolean
compose_beautify_paragraph(Compose
*compose
, GtkTextIter
*par_iter
, gboolean force
)
4364 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
4365 GtkTextBuffer
*buffer
;
4366 GtkTextIter iter
, break_pos
, end_of_line
;
4367 gchar
*quote_str
= NULL
;
4369 gboolean wrap_quote
= force
|| prefs_common
.linewrap_quote
;
4370 gboolean prev_autowrap
= compose
->autowrap
;
4371 gint startq_offset
= -1, noq_offset
= -1;
4372 gint uri_start
= -1, uri_stop
= -1;
4373 gint nouri_start
= -1, nouri_stop
= -1;
4374 gint num_blocks
= 0;
4375 gint quotelevel
= -1;
4376 gboolean modified
= force
;
4377 gboolean removed
= FALSE
;
4378 gboolean modified_before_remove
= FALSE
;
4380 gboolean start
= TRUE
;
4381 gint itemized_len
= 0, rem_item_len
= 0;
4382 gchar
*itemized_chars
= NULL
;
4383 gboolean item_continuation
= FALSE
;
4388 if (compose
->draft_timeout_tag
== COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
) {
4392 compose
->autowrap
= FALSE
;
4394 buffer
= gtk_text_view_get_buffer(text
);
4395 undo_wrapping(compose
->undostruct
, TRUE
);
4400 mark
= gtk_text_buffer_get_insert(buffer
);
4401 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
4405 if (compose
->draft_timeout_tag
== COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
) {
4406 if (gtk_text_iter_ends_line(&iter
)) {
4407 while (gtk_text_iter_ends_line(&iter
) &&
4408 gtk_text_iter_forward_line(&iter
))
4411 while (gtk_text_iter_backward_line(&iter
)) {
4412 if (gtk_text_iter_ends_line(&iter
)) {
4413 gtk_text_iter_forward_line(&iter
);
4419 /* move to line start */
4420 gtk_text_iter_set_line_offset(&iter
, 0);
4423 itemized_len
= compose_itemized_length(buffer
, &iter
);
4425 if (!itemized_len
) {
4426 itemized_len
= compose_left_offset_length(buffer
, &iter
);
4427 item_continuation
= TRUE
;
4431 itemized_chars
= compose_get_itemized_chars(buffer
, &iter
);
4433 /* go until paragraph end (empty line) */
4434 while (start
|| !gtk_text_iter_ends_line(&iter
)) {
4435 gchar
*scanpos
= NULL
;
4436 /* parse table - in order of priority */
4438 const gchar
*needle
; /* token */
4440 /* token search function */
4441 gchar
*(*search
) (const gchar
*haystack
,
4442 const gchar
*needle
);
4443 /* part parsing function */
4444 gboolean (*parse
) (const gchar
*start
,
4445 const gchar
*scanpos
,
4449 /* part to URI function */
4450 gchar
*(*build_uri
) (const gchar
*bp
,
4454 static struct table parser
[] = {
4455 {"http://", strcasestr
, get_uri_part
, make_uri_string
},
4456 {"https://", strcasestr
, get_uri_part
, make_uri_string
},
4457 {"ftp://", strcasestr
, get_uri_part
, make_uri_string
},
4458 {"sftp://", strcasestr
, get_uri_part
, make_uri_string
},
4459 {"gopher://",strcasestr
, get_uri_part
, make_uri_string
},
4460 {"www.", strcasestr
, get_uri_part
, make_http_string
},
4461 {"mailto:", strcasestr
, get_uri_part
, make_uri_string
},
4462 {"@", strcasestr
, get_email_part
, make_email_string
}
4464 const gint PARSE_ELEMS
= sizeof parser
/ sizeof parser
[0];
4465 gint last_index
= PARSE_ELEMS
;
4467 gchar
*o_walk
= NULL
, *walk
= NULL
, *bp
= NULL
, *ep
= NULL
;
4471 if (!prev_autowrap
&& num_blocks
== 0) {
4473 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
4474 G_CALLBACK(text_inserted
),
4477 if (gtk_text_iter_has_tag(&iter
, compose
->no_wrap_tag
) && !force
)
4480 uri_start
= uri_stop
= -1;
4482 quote_str
= compose_get_quote_str(buffer
, &iter
, "e_len
);
4485 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4486 if (startq_offset
== -1)
4487 startq_offset
= gtk_text_iter_get_offset(&iter
);
4488 quotelevel
= get_quote_level(quote_str
, prefs_common
.quote_chars
);
4489 if (quotelevel
> 2) {
4490 /* recycle colors */
4491 if (prefs_common
.recycle_quote_colors
)
4500 if (startq_offset
== -1)
4501 noq_offset
= gtk_text_iter_get_offset(&iter
);
4505 if (prev_autowrap
== FALSE
&& !force
&& !wrap_quote
) {
4508 if (gtk_text_iter_ends_line(&iter
)) {
4510 } else if (compose_get_line_break_pos(buffer
, &iter
, &break_pos
,
4511 prefs_common
.linewrap_len
,
4513 GtkTextIter prev
, next
, cur
;
4514 if (prev_autowrap
!= FALSE
|| force
) {
4515 compose
->automatic_break
= TRUE
;
4517 gtk_text_buffer_insert(buffer
, &break_pos
, "\n", 1);
4518 compose
->automatic_break
= FALSE
;
4519 if (itemized_len
&& compose
->autoindent
) {
4520 gtk_text_buffer_insert(buffer
, &break_pos
, itemized_chars
, -1);
4521 if (!item_continuation
)
4522 gtk_text_buffer_insert(buffer
, &break_pos
, " ", 2);
4524 } else if (quote_str
&& wrap_quote
) {
4525 compose
->automatic_break
= TRUE
;
4527 gtk_text_buffer_insert(buffer
, &break_pos
, "\n", 1);
4528 compose
->automatic_break
= FALSE
;
4529 if (itemized_len
&& compose
->autoindent
) {
4530 gtk_text_buffer_insert(buffer
, &break_pos
, itemized_chars
, -1);
4531 if (!item_continuation
)
4532 gtk_text_buffer_insert(buffer
, &break_pos
, " ", 2);
4536 /* remove trailing spaces */
4538 rem_item_len
= itemized_len
;
4539 while (compose
->autoindent
&& rem_item_len
-- > 0)
4540 gtk_text_iter_backward_char(&cur
);
4541 gtk_text_iter_backward_char(&cur
);
4544 while (!gtk_text_iter_starts_line(&cur
)) {
4547 gtk_text_iter_backward_char(&cur
);
4548 wc
= gtk_text_iter_get_char(&cur
);
4549 if (!g_unichar_isspace(wc
))
4553 if (!gtk_text_iter_equal(&prev
, &next
)) {
4554 gtk_text_buffer_delete(buffer
, &prev
, &next
);
4556 gtk_text_iter_forward_char(&break_pos
);
4560 gtk_text_buffer_insert(buffer
, &break_pos
,
4564 modified
|= compose_join_next_line(compose
, buffer
, &iter
, quote_str
);
4566 /* move iter to current line start */
4567 gtk_text_iter_set_line_offset(&iter
, 0);
4574 /* move iter to next line start */
4580 if (!prev_autowrap
&& num_blocks
> 0) {
4582 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
4583 G_CALLBACK(text_inserted
),
4587 while (!gtk_text_iter_ends_line(&end_of_line
)) {
4588 gtk_text_iter_forward_char(&end_of_line
);
4590 o_walk
= walk
= gtk_text_buffer_get_text(buffer
, &iter
, &end_of_line
, FALSE
);
4592 nouri_start
= gtk_text_iter_get_offset(&iter
);
4593 nouri_stop
= gtk_text_iter_get_offset(&end_of_line
);
4595 walk_pos
= gtk_text_iter_get_offset(&iter
);
4596 /* FIXME: this looks phony. scanning for anything in the parse table */
4597 for (n
= 0; n
< PARSE_ELEMS
; n
++) {
4600 tmp
= parser
[n
].search(walk
, parser
[n
].needle
);
4602 if (scanpos
== NULL
|| tmp
< scanpos
) {
4611 /* check if URI can be parsed */
4612 if (parser
[last_index
].parse(walk
, scanpos
, (const gchar
**)&bp
,
4613 (const gchar
**)&ep
, FALSE
)
4614 && (size_t) (ep
- bp
- 1) > strlen(parser
[last_index
].needle
)) {
4618 strlen(parser
[last_index
].needle
);
4621 uri_start
= walk_pos
+ (bp
- o_walk
);
4622 uri_stop
= walk_pos
+ (ep
- o_walk
);
4626 gtk_text_iter_forward_line(&iter
);
4629 if (startq_offset
!= -1) {
4630 GtkTextIter startquote
, endquote
;
4631 gtk_text_buffer_get_iter_at_offset(
4632 buffer
, &startquote
, startq_offset
);
4635 switch (quotelevel
) {
4637 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote0_tag
) ||
4638 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote0_tag
)) {
4639 gtk_text_buffer_apply_tag_by_name(
4640 buffer
, "quote0", &startquote
, &endquote
);
4641 gtk_text_buffer_remove_tag_by_name(
4642 buffer
, "quote1", &startquote
, &endquote
);
4643 gtk_text_buffer_remove_tag_by_name(
4644 buffer
, "quote2", &startquote
, &endquote
);
4649 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote1_tag
) ||
4650 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote1_tag
)) {
4651 gtk_text_buffer_apply_tag_by_name(
4652 buffer
, "quote1", &startquote
, &endquote
);
4653 gtk_text_buffer_remove_tag_by_name(
4654 buffer
, "quote0", &startquote
, &endquote
);
4655 gtk_text_buffer_remove_tag_by_name(
4656 buffer
, "quote2", &startquote
, &endquote
);
4661 if (!gtk_text_iter_has_tag(&startquote
, compose
->quote2_tag
) ||
4662 !gtk_text_iter_has_tag(&end_of_line
, compose
->quote2_tag
)) {
4663 gtk_text_buffer_apply_tag_by_name(
4664 buffer
, "quote2", &startquote
, &endquote
);
4665 gtk_text_buffer_remove_tag_by_name(
4666 buffer
, "quote0", &startquote
, &endquote
);
4667 gtk_text_buffer_remove_tag_by_name(
4668 buffer
, "quote1", &startquote
, &endquote
);
4674 } else if (noq_offset
!= -1) {
4675 GtkTextIter startnoquote
, endnoquote
;
4676 gtk_text_buffer_get_iter_at_offset(
4677 buffer
, &startnoquote
, noq_offset
);
4680 if ((gtk_text_iter_has_tag(&startnoquote
, compose
->quote0_tag
)
4681 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote0_tag
)) ||
4682 (gtk_text_iter_has_tag(&startnoquote
, compose
->quote1_tag
)
4683 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote1_tag
)) ||
4684 (gtk_text_iter_has_tag(&startnoquote
, compose
->quote2_tag
)
4685 && gtk_text_iter_has_tag(&end_of_line
, compose
->quote2_tag
))) {
4686 gtk_text_buffer_remove_tag_by_name(
4687 buffer
, "quote0", &startnoquote
, &endnoquote
);
4688 gtk_text_buffer_remove_tag_by_name(
4689 buffer
, "quote1", &startnoquote
, &endnoquote
);
4690 gtk_text_buffer_remove_tag_by_name(
4691 buffer
, "quote2", &startnoquote
, &endnoquote
);
4697 if (uri_start
!= nouri_start
&& uri_stop
!= nouri_stop
) {
4698 GtkTextIter nouri_start_iter
, nouri_end_iter
;
4699 gtk_text_buffer_get_iter_at_offset(
4700 buffer
, &nouri_start_iter
, nouri_start
);
4701 gtk_text_buffer_get_iter_at_offset(
4702 buffer
, &nouri_end_iter
, nouri_stop
);
4703 if (gtk_text_iter_has_tag(&nouri_start_iter
, compose
->uri_tag
) &&
4704 gtk_text_iter_has_tag(&nouri_end_iter
, compose
->uri_tag
)) {
4705 gtk_text_buffer_remove_tag_by_name(
4706 buffer
, "link", &nouri_start_iter
, &nouri_end_iter
);
4707 modified_before_remove
= modified
;
4712 if (uri_start
>= 0 && uri_stop
> 0) {
4713 GtkTextIter uri_start_iter
, uri_end_iter
, back
;
4714 gtk_text_buffer_get_iter_at_offset(
4715 buffer
, &uri_start_iter
, uri_start
);
4716 gtk_text_buffer_get_iter_at_offset(
4717 buffer
, &uri_end_iter
, uri_stop
);
4718 back
= uri_end_iter
;
4719 gtk_text_iter_backward_char(&back
);
4720 if (!gtk_text_iter_has_tag(&uri_start_iter
, compose
->uri_tag
) ||
4721 !gtk_text_iter_has_tag(&back
, compose
->uri_tag
)) {
4722 gtk_text_buffer_apply_tag_by_name(
4723 buffer
, "link", &uri_start_iter
, &uri_end_iter
);
4725 if (removed
&& !modified_before_remove
) {
4731 // debug_print("not modified, out after %d lines\n", lines);
4735 // debug_print("modified, out after %d lines\n", lines);
4737 g_free(itemized_chars
);
4740 undo_wrapping(compose
->undostruct
, FALSE
);
4741 compose
->autowrap
= prev_autowrap
;
4746 void compose_action_cb(void *data
)
4748 Compose
*compose
= (Compose
*)data
;
4749 compose_wrap_all(compose
);
4752 static void compose_wrap_all(Compose
*compose
)
4754 compose_wrap_all_full(compose
, FALSE
);
4757 static void compose_wrap_all_full(Compose
*compose
, gboolean force
)
4759 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
4760 GtkTextBuffer
*buffer
;
4762 gboolean modified
= TRUE
;
4764 buffer
= gtk_text_view_get_buffer(text
);
4766 gtk_text_buffer_get_start_iter(buffer
, &iter
);
4768 undo_wrapping(compose
->undostruct
, TRUE
);
4770 while (!gtk_text_iter_is_end(&iter
) && modified
)
4771 modified
= compose_beautify_paragraph(compose
, &iter
, force
);
4773 undo_wrapping(compose
->undostruct
, FALSE
);
4777 static void compose_set_title(Compose
*compose
)
4783 edited
= compose
->modified
? _(" [Edited]") : "";
4785 subject
= gtk_editable_get_chars(
4786 GTK_EDITABLE(compose
->subject_entry
), 0, -1);
4788 #ifndef GENERIC_UMPC
4789 if (subject
&& strlen(subject
))
4790 str
= g_strdup_printf(_("%s - Compose message%s"),
4793 str
= g_strdup_printf(_("[no subject] - Compose message%s"), edited
);
4795 str
= g_strdup(_("Compose message"));
4798 gtk_window_set_title(GTK_WINDOW(compose
->window
), str
);
4804 * compose_current_mail_account:
4806 * Find a current mail account (the currently selected account, or the
4807 * default account, if a news account is currently selected). If a
4808 * mail account cannot be found, display an error message.
4810 * Return value: Mail account, or NULL if not found.
4812 static PrefsAccount
*
4813 compose_current_mail_account(void)
4817 if (cur_account
&& cur_account
->protocol
!= A_NNTP
)
4820 ac
= account_get_default();
4821 if (!ac
|| ac
->protocol
== A_NNTP
) {
4822 alertpanel_error(_("Account for sending mail is not specified.\n"
4823 "Please select a mail account before sending."));
4830 #define QUOTE_IF_REQUIRED(out, str) \
4832 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4836 len = strlen(str) + 3; \
4837 if ((__tmp = alloca(len)) == NULL) { \
4838 g_warning("can't allocate memory"); \
4839 g_string_free(header, TRUE); \
4842 g_snprintf(__tmp, len, "\"%s\"", str); \
4847 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4848 g_warning("can't allocate memory"); \
4849 g_string_free(header, TRUE); \
4852 strcpy(__tmp, str); \
4858 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4860 if (*str != '"' && strpbrk(str, ",.:;[]<>()@\\\"")) { \
4864 len = strlen(str) + 3; \
4865 if ((__tmp = alloca(len)) == NULL) { \
4866 g_warning("can't allocate memory"); \
4869 g_snprintf(__tmp, len, "\"%s\"", str); \
4874 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4875 g_warning("can't allocate memory"); \
4878 strcpy(__tmp, str); \
4884 static void compose_select_account(Compose
*compose
, PrefsAccount
*account
,
4887 gchar
*from
= NULL
, *header
= NULL
;
4888 ComposeHeaderEntry
*header_entry
;
4889 #if GTK_CHECK_VERSION(2, 24, 0)
4893 cm_return_if_fail(account
!= NULL
);
4895 compose
->account
= account
;
4896 if (account
->name
&& *account
->name
) {
4898 QUOTE_IF_REQUIRED_NORMAL(buf
, account
->name
, return);
4899 qbuf
= escape_internal_quotes(buf
, '"');
4900 from
= g_strdup_printf("%s <%s>",
4901 qbuf
, account
->address
);
4904 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), from
);
4906 from
= g_strdup_printf("<%s>",
4908 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), from
);
4913 compose_set_title(compose
);
4915 if (account
->default_sign
&& compose
->mode
!= COMPOSE_REDIRECT
)
4916 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", TRUE
);
4918 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Sign", FALSE
);
4919 if (account
->default_encrypt
&& compose
->mode
!= COMPOSE_REDIRECT
)
4920 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", TRUE
);
4922 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Options/Encrypt", FALSE
);
4924 activate_privacy_system(compose
, account
, FALSE
);
4926 if (!init
&& compose
->mode
!= COMPOSE_REDIRECT
) {
4927 undo_block(compose
->undostruct
);
4928 compose_insert_sig(compose
, TRUE
);
4929 undo_unblock(compose
->undostruct
);
4932 header_entry
= (ComposeHeaderEntry
*) compose
->header_list
->data
;
4933 #if !GTK_CHECK_VERSION(2, 24, 0)
4934 header
= gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry
->combo
));
4936 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry
->combo
), &iter
))
4937 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4938 header_entry
->combo
)), &iter
, COMBOBOX_TEXT
, &header
, -1);
4941 if (header
&& !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry
->entry
)))) {
4942 if (account
->protocol
== A_NNTP
) {
4943 if (!strcmp(header
, _("To:")))
4944 combobox_select_by_text(
4945 GTK_COMBO_BOX(header_entry
->combo
),
4948 if (!strcmp(header
, _("Newsgroups:")))
4949 combobox_select_by_text(
4950 GTK_COMBO_BOX(header_entry
->combo
),
4958 /* use account's dict info if set */
4959 if (compose
->gtkaspell
) {
4960 if (account
->enable_default_dictionary
)
4961 gtkaspell_change_dict(compose
->gtkaspell
,
4962 account
->default_dictionary
, FALSE
);
4963 if (account
->enable_default_alt_dictionary
)
4964 gtkaspell_change_alt_dict(compose
->gtkaspell
,
4965 account
->default_alt_dictionary
);
4966 if (account
->enable_default_dictionary
4967 || account
->enable_default_alt_dictionary
)
4968 compose_spell_menu_changed(compose
);
4973 gboolean
compose_check_for_valid_recipient(Compose
*compose
) {
4974 gchar
*recipient_headers_mail
[] = {"To:", "Cc:", "Bcc:", NULL
};
4975 gchar
*recipient_headers_news
[] = {"Newsgroups:", NULL
};
4976 gboolean recipient_found
= FALSE
;
4980 /* free to and newsgroup list */
4981 slist_free_strings_full(compose
->to_list
);
4982 compose
->to_list
= NULL
;
4984 slist_free_strings_full(compose
->newsgroup_list
);
4985 compose
->newsgroup_list
= NULL
;
4987 /* search header entries for to and newsgroup entries */
4988 for (list
= compose
->header_list
; list
; list
= list
->next
) {
4991 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
4992 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
4995 if (entry
[0] != '\0') {
4996 for (strptr
= recipient_headers_mail
; *strptr
!= NULL
; strptr
++) {
4997 if (!g_ascii_strcasecmp(header
, prefs_common_translated_header_name(*strptr
))) {
4998 compose
->to_list
= address_list_append(compose
->to_list
, entry
);
4999 recipient_found
= TRUE
;
5002 for (strptr
= recipient_headers_news
; *strptr
!= NULL
; strptr
++) {
5003 if (!g_ascii_strcasecmp(header
, prefs_common_translated_header_name(*strptr
))) {
5004 compose
->newsgroup_list
= newsgroup_list_append(compose
->newsgroup_list
, entry
);
5005 recipient_found
= TRUE
;
5012 return recipient_found
;
5015 static gboolean
compose_check_for_set_recipients(Compose
*compose
)
5017 if (compose
->account
->set_autocc
&& compose
->account
->auto_cc
) {
5018 gboolean found_other
= FALSE
;
5020 /* search header entries for to and newsgroup entries */
5021 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5024 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5025 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5028 if (strcmp(entry
, compose
->account
->auto_cc
)
5029 || strcmp(header
, prefs_common_translated_header_name("Cc:"))) {
5039 if (compose
->batch
) {
5040 gtk_widget_show_all(compose
->window
);
5042 aval
= alertpanel(_("Send"),
5043 _("The only recipient is the default CC address. Send anyway?"),
5044 GTK_STOCK_CANCEL
, g_strconcat("+", _("_Send"), NULL
), NULL
);
5045 if (aval
!= G_ALERTALTERNATE
)
5049 if (compose
->account
->set_autobcc
&& compose
->account
->auto_bcc
) {
5050 gboolean found_other
= FALSE
;
5052 /* search header entries for to and newsgroup entries */
5053 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5056 entry
= gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
5057 header
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
5060 if (strcmp(entry
, compose
->account
->auto_bcc
)
5061 || strcmp(header
, prefs_common_translated_header_name("Bcc:"))) {
5071 if (compose
->batch
) {
5072 gtk_widget_show_all(compose
->window
);
5074 aval
= alertpanel(_("Send"),
5075 _("The only recipient is the default BCC address. Send anyway?"),
5076 GTK_STOCK_CANCEL
, g_strconcat("+", _("_Send"), NULL
), NULL
);
5077 if (aval
!= G_ALERTALTERNATE
)
5084 static gboolean
compose_check_entries(Compose
*compose
, gboolean check_everything
)
5088 if (compose_check_for_valid_recipient(compose
) == FALSE
) {
5089 if (compose
->batch
) {
5090 gtk_widget_show_all(compose
->window
);
5092 alertpanel_error(_("Recipient is not specified."));
5096 if (compose_check_for_set_recipients(compose
) == FALSE
) {
5100 if (!compose
->batch
&& prefs_common
.warn_empty_subj
== TRUE
) {
5101 str
= gtk_entry_get_text(GTK_ENTRY(compose
->subject_entry
));
5102 if (*str
== '\0' && check_everything
== TRUE
&&
5103 compose
->mode
!= COMPOSE_REDIRECT
) {
5105 gchar
*button_label
;
5108 if (compose
->sending
)
5109 button_label
= g_strconcat("+", _("_Send"), NULL
);
5111 button_label
= g_strconcat("+", _("_Queue"), NULL
);
5112 message
= g_strdup_printf(_("Subject is empty. %s"),
5113 compose
->sending
?_("Send it anyway?"):
5114 _("Queue it anyway?"));
5116 aval
= alertpanel_full(compose
->sending
?_("Send"):_("Send later"), message
,
5117 GTK_STOCK_CANCEL
, button_label
, NULL
, TRUE
, NULL
,
5118 ALERT_QUESTION
, G_ALERTDEFAULT
);
5120 if (aval
& G_ALERTDISABLE
) {
5121 aval
&= ~G_ALERTDISABLE
;
5122 prefs_common
.warn_empty_subj
= FALSE
;
5124 if (aval
!= G_ALERTALTERNATE
)
5129 if (check_everything
&& hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST
, compose
))
5135 gint
compose_send(Compose
*compose
)
5138 FolderItem
*folder
= NULL
;
5140 gchar
*msgpath
= NULL
;
5141 gboolean discard_window
= FALSE
;
5142 gchar
*errstr
= NULL
;
5143 gchar
*tmsgid
= NULL
;
5144 MainWindow
*mainwin
= mainwindow_get_mainwindow();
5145 gboolean queued_removed
= FALSE
;
5147 if (prefs_common
.send_dialog_invisible
5148 || compose
->batch
== TRUE
)
5149 discard_window
= TRUE
;
5151 compose_allow_user_actions (compose
, FALSE
);
5152 compose
->sending
= TRUE
;
5154 if (compose_check_entries(compose
, TRUE
) == FALSE
) {
5155 if (compose
->batch
) {
5156 gtk_widget_show_all(compose
->window
);
5162 val
= compose_queue(compose
, &msgnum
, &folder
, &msgpath
, TRUE
);
5165 if (compose
->batch
) {
5166 gtk_widget_show_all(compose
->window
);
5169 alertpanel_error(_("Could not queue message for sending:\n\n"
5170 "Charset conversion failed."));
5171 } else if (val
== -5) {
5172 alertpanel_error(_("Could not queue message for sending:\n\n"
5173 "Couldn't get recipient encryption key."));
5174 } else if (val
== -6) {
5176 } else if (val
== -3) {
5177 if (privacy_peek_error())
5178 alertpanel_error(_("Could not queue message for sending:\n\n"
5179 "Signature failed: %s"), privacy_get_error());
5180 } else if (val
== -2 && errno
!= 0) {
5181 alertpanel_error(_("Could not queue message for sending:\n\n%s."), g_strerror(errno
));
5183 alertpanel_error(_("Could not queue message for sending."));
5188 tmsgid
= compose
->msgid
? g_strdup(compose
->msgid
) : NULL
;
5189 if (discard_window
) {
5190 compose
->sending
= FALSE
;
5191 compose_close(compose
);
5192 /* No more compose access in the normal codepath
5193 * after this point! */
5198 alertpanel_error(_("The message was queued but could not be "
5199 "sent.\nUse \"Send queued messages\" from "
5200 "the main window to retry."));
5201 if (!discard_window
) {
5208 if (msgpath
== NULL
) {
5209 msgpath
= folder_item_fetch_msg(folder
, msgnum
);
5210 val
= procmsg_send_message_queue_with_lock(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
5213 val
= procmsg_send_message_queue_with_lock(msgpath
, &errstr
, folder
, msgnum
, &queued_removed
);
5214 claws_unlink(msgpath
);
5217 if (!discard_window
) {
5219 if (!queued_removed
)
5220 folder_item_remove_msg(folder
, msgnum
);
5221 folder_item_scan(folder
);
5223 /* make sure we delete that */
5224 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
5226 debug_print("removing %d via %s\n", tmp
->msgnum
, tmsgid
);
5227 folder_item_remove_msg(folder
, tmp
->msgnum
);
5228 procmsg_msginfo_free(&tmp
);
5235 if (!queued_removed
)
5236 folder_item_remove_msg(folder
, msgnum
);
5237 folder_item_scan(folder
);
5239 /* make sure we delete that */
5240 MsgInfo
*tmp
= folder_item_get_msginfo_by_msgid(folder
, tmsgid
);
5242 debug_print("removing %d via %s\n", tmp
->msgnum
, tmsgid
);
5243 folder_item_remove_msg(folder
, tmp
->msgnum
);
5244 procmsg_msginfo_free(&tmp
);
5247 if (!discard_window
) {
5248 compose
->sending
= FALSE
;
5249 compose_allow_user_actions (compose
, TRUE
);
5250 compose_close(compose
);
5254 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5255 "the main window to retry."), errstr
);
5258 alertpanel_error_log(_("The message was queued but could not be "
5259 "sent.\nUse \"Send queued messages\" from "
5260 "the main window to retry."));
5262 if (!discard_window
) {
5271 toolbar_main_set_sensitive(mainwin
);
5272 main_window_set_menu_sensitive(mainwin
);
5278 compose_allow_user_actions (compose
, TRUE
);
5279 compose
->sending
= FALSE
;
5280 compose
->modified
= TRUE
;
5281 toolbar_main_set_sensitive(mainwin
);
5282 main_window_set_menu_sensitive(mainwin
);
5287 static gboolean
compose_use_attach(Compose
*compose
)
5289 GtkTreeModel
*model
= gtk_tree_view_get_model
5290 (GTK_TREE_VIEW(compose
->attach_clist
));
5291 return gtk_tree_model_iter_n_children(model
, NULL
) > 0;
5294 static gint
compose_redirect_write_headers_from_headerlist(Compose
*compose
,
5297 gchar buf
[BUFFSIZE
];
5299 gboolean first_to_address
;
5300 gboolean first_cc_address
;
5302 ComposeHeaderEntry
*headerentry
;
5303 const gchar
*headerentryname
;
5304 const gchar
*cc_hdr
;
5305 const gchar
*to_hdr
;
5306 gboolean err
= FALSE
;
5308 debug_print("Writing redirect header\n");
5310 cc_hdr
= prefs_common_translated_header_name("Cc:");
5311 to_hdr
= prefs_common_translated_header_name("To:");
5313 first_to_address
= TRUE
;
5314 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5315 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
5316 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
5318 if (g_utf8_collate(headerentryname
, to_hdr
) == 0) {
5319 const gchar
*entstr
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
5320 Xstrdup_a(str
, entstr
, return -1);
5322 if (str
[0] != '\0') {
5323 compose_convert_header
5324 (compose
, buf
, sizeof(buf
), str
,
5325 strlen("Resent-To") + 2, TRUE
);
5327 if (first_to_address
) {
5328 err
|= (fprintf(fp
, "Resent-To: ") < 0);
5329 first_to_address
= FALSE
;
5331 err
|= (fprintf(fp
, ",") < 0);
5333 err
|= (fprintf(fp
, "%s", buf
) < 0);
5337 if (!first_to_address
) {
5338 err
|= (fprintf(fp
, "\n") < 0);
5341 first_cc_address
= TRUE
;
5342 for (list
= compose
->header_list
; list
; list
= list
->next
) {
5343 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
5344 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
5346 if (g_utf8_collate(headerentryname
, cc_hdr
) == 0) {
5347 const gchar
*strg
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
5348 Xstrdup_a(str
, strg
, return -1);
5350 if (str
[0] != '\0') {
5351 compose_convert_header
5352 (compose
, buf
, sizeof(buf
), str
,
5353 strlen("Resent-Cc") + 2, TRUE
);
5355 if (first_cc_address
) {
5356 err
|= (fprintf(fp
, "Resent-Cc: ") < 0);
5357 first_cc_address
= FALSE
;
5359 err
|= (fprintf(fp
, ",") < 0);
5361 err
|= (fprintf(fp
, "%s", buf
) < 0);
5365 if (!first_cc_address
) {
5366 err
|= (fprintf(fp
, "\n") < 0);
5369 return (err
? -1:0);
5372 static gint
compose_redirect_write_headers(Compose
*compose
, FILE *fp
)
5374 gchar buf
[BUFFSIZE
];
5376 const gchar
*entstr
;
5377 /* struct utsname utsbuf; */
5378 gboolean err
= FALSE
;
5380 cm_return_val_if_fail(fp
!= NULL
, -1);
5381 cm_return_val_if_fail(compose
->account
!= NULL
, -1);
5382 cm_return_val_if_fail(compose
->account
->address
!= NULL
, -1);
5385 if (prefs_common
.hide_timezone
)
5386 get_rfc822_date_hide_tz(buf
, sizeof(buf
));
5388 get_rfc822_date(buf
, sizeof(buf
));
5389 err
|= (fprintf(fp
, "Resent-Date: %s\n", buf
) < 0);
5392 if (compose
->account
->name
&& *compose
->account
->name
) {
5393 compose_convert_header
5394 (compose
, buf
, sizeof(buf
), compose
->account
->name
,
5395 strlen("From: "), TRUE
);
5396 err
|= (fprintf(fp
, "Resent-From: %s <%s>\n",
5397 buf
, compose
->account
->address
) < 0);
5399 err
|= (fprintf(fp
, "Resent-From: %s\n", compose
->account
->address
) < 0);
5402 entstr
= gtk_entry_get_text(GTK_ENTRY(compose
->subject_entry
));
5403 if (*entstr
!= '\0') {
5404 Xstrdup_a(str
, entstr
, return -1);
5407 compose_convert_header(compose
, buf
, sizeof(buf
), str
,
5408 strlen("Subject: "), FALSE
);
5409 err
|= (fprintf(fp
, "Subject: %s\n", buf
) < 0);
5413 /* Resent-Message-ID */
5414 if (compose
->account
->set_domain
&& compose
->account
->domain
) {
5415 g_snprintf(buf
, sizeof(buf
), "%s", compose
->account
->domain
);
5416 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5417 g_snprintf(buf
, sizeof(buf
), "%s",
5418 strchr(compose
->account
->address
, '@') ?
5419 strchr(compose
->account
->address
, '@')+1 :
5420 compose
->account
->address
);
5422 g_snprintf(buf
, sizeof(buf
), "%s", "");
5425 if (compose
->account
->gen_msgid
) {
5427 if (compose
->account
->msgid_with_addr
) {
5428 addr
= compose
->account
->address
;
5430 generate_msgid(buf
, sizeof(buf
), addr
);
5431 err
|= (fprintf(fp
, "Resent-Message-ID: <%s>\n", buf
) < 0);
5433 g_free(compose
->msgid
);
5434 compose
->msgid
= g_strdup(buf
);
5436 compose
->msgid
= NULL
;
5439 if (compose_redirect_write_headers_from_headerlist(compose
, fp
))
5442 /* separator between header and body */
5443 err
|= (fputs("\n", fp
) == EOF
);
5445 return (err
? -1:0);
5448 static gint
compose_redirect_write_to_file(Compose
*compose
, FILE *fdest
)
5452 gchar buf
[BUFFSIZE
];
5454 gboolean skip
= FALSE
;
5455 gboolean err
= FALSE
;
5456 gchar
*not_included
[]={
5457 "Return-Path:", "Delivered-To:", "Received:",
5458 "Subject:", "X-UIDL:", "AF:",
5459 "NF:", "PS:", "SRH:",
5460 "SFN:", "DSR:", "MID:",
5461 "CFG:", "PT:", "S:",
5462 "RQ:", "SSV:", "NSV:",
5463 "SSH:", "R:", "MAID:",
5464 "NAID:", "RMID:", "FMID:",
5465 "SCF:", "RRCPT:", "NG:",
5466 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5467 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5468 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5469 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5470 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5473 if ((fp
= g_fopen(compose
->redirect_filename
, "rb")) == NULL
) {
5474 FILE_OP_ERROR(compose
->redirect_filename
, "fopen");
5478 while (procheader_get_one_field_asis(buf
, sizeof(buf
), fp
) != -1) {
5480 for (i
= 0; not_included
[i
] != NULL
; i
++) {
5481 if (g_ascii_strncasecmp(buf
, not_included
[i
],
5482 strlen(not_included
[i
])) == 0) {
5489 if (fputs(buf
, fdest
) == -1)
5492 if (!prefs_common
.redirect_keep_from
) {
5493 if (g_ascii_strncasecmp(buf
, "From:",
5494 strlen("From:")) == 0) {
5495 err
|= (fputs(" (by way of ", fdest
) == EOF
);
5496 if (compose
->account
->name
5497 && *compose
->account
->name
) {
5498 compose_convert_header
5499 (compose
, buf
, sizeof(buf
),
5500 compose
->account
->name
,
5503 err
|= (fprintf(fdest
, "%s <%s>",
5505 compose
->account
->address
) < 0);
5507 err
|= (fprintf(fdest
, "%s",
5508 compose
->account
->address
) < 0);
5509 err
|= (fputs(")", fdest
) == EOF
);
5513 if (fputs("\n", fdest
) == -1)
5520 if (compose_redirect_write_headers(compose
, fdest
))
5523 while ((len
= fread(buf
, sizeof(gchar
), sizeof(buf
), fp
)) > 0) {
5524 if (fwrite(buf
, sizeof(gchar
), len
, fdest
) != len
)
5537 static gint
compose_write_to_file(Compose
*compose
, FILE *fp
, gint action
, gboolean attach_parts
)
5539 GtkTextBuffer
*buffer
;
5540 GtkTextIter start
, end
;
5541 gchar
*chars
, *tmp_enc_file
, *content
;
5543 const gchar
*out_codeset
;
5544 EncodingType encoding
= ENC_UNKNOWN
;
5545 MimeInfo
*mimemsg
, *mimetext
;
5547 const gchar
*src_codeset
= CS_INTERNAL
;
5548 gchar
*from_addr
= NULL
;
5549 gchar
*from_name
= NULL
;
5552 if (action
== COMPOSE_WRITE_FOR_SEND
)
5553 attach_parts
= TRUE
;
5555 /* create message MimeInfo */
5556 mimemsg
= procmime_mimeinfo_new();
5557 mimemsg
->type
= MIMETYPE_MESSAGE
;
5558 mimemsg
->subtype
= g_strdup("rfc822");
5559 mimemsg
->content
= MIMECONTENT_MEM
;
5560 mimemsg
->tmp
= TRUE
; /* must free content later */
5561 mimemsg
->data
.mem
= compose_get_header(compose
);
5563 /* Create text part MimeInfo */
5564 /* get all composed text */
5565 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
5566 gtk_text_buffer_get_start_iter(buffer
, &start
);
5567 gtk_text_buffer_get_end_iter(buffer
, &end
);
5568 chars
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
5570 out_codeset
= conv_get_charset_str(compose
->out_encoding
);
5572 if (!out_codeset
&& is_ascii_str(chars
)) {
5573 out_codeset
= CS_US_ASCII
;
5574 } else if (prefs_common
.outgoing_fallback_to_ascii
&&
5575 is_ascii_str(chars
)) {
5576 out_codeset
= CS_US_ASCII
;
5577 encoding
= ENC_7BIT
;
5581 gchar
*test_conv_global_out
= NULL
;
5582 gchar
*test_conv_reply
= NULL
;
5584 /* automatic mode. be automatic. */
5585 codeconv_set_strict(TRUE
);
5587 out_codeset
= conv_get_outgoing_charset_str();
5589 debug_print("trying to convert to %s\n", out_codeset
);
5590 test_conv_global_out
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5593 if (!test_conv_global_out
&& compose
->orig_charset
5594 && strcmp(compose
->orig_charset
, CS_US_ASCII
)) {
5595 out_codeset
= compose
->orig_charset
;
5596 debug_print("failure; trying to convert to %s\n", out_codeset
);
5597 test_conv_reply
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5600 if (!test_conv_global_out
&& !test_conv_reply
) {
5602 out_codeset
= CS_INTERNAL
;
5603 debug_print("failure; finally using %s\n", out_codeset
);
5605 g_free(test_conv_global_out
);
5606 g_free(test_conv_reply
);
5607 codeconv_set_strict(FALSE
);
5610 if (encoding
== ENC_UNKNOWN
) {
5611 if (prefs_common
.encoding_method
== CTE_BASE64
)
5612 encoding
= ENC_BASE64
;
5613 else if (prefs_common
.encoding_method
== CTE_QUOTED_PRINTABLE
)
5614 encoding
= ENC_QUOTED_PRINTABLE
;
5615 else if (prefs_common
.encoding_method
== CTE_8BIT
)
5616 encoding
= ENC_8BIT
;
5618 encoding
= procmime_get_encoding_for_charset(out_codeset
);
5621 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5622 src_codeset
, out_codeset
, procmime_get_encoding_str(encoding
));
5624 if (action
== COMPOSE_WRITE_FOR_SEND
) {
5625 codeconv_set_strict(TRUE
);
5626 buf
= conv_codeset_strdup(chars
, src_codeset
, out_codeset
);
5627 codeconv_set_strict(FALSE
);
5632 msg
= g_strdup_printf(_("Can't convert the character encoding of the message \n"
5633 "to the specified %s charset.\n"
5634 "Send it as %s?"), out_codeset
, src_codeset
);
5635 aval
= alertpanel_full(_("Error"), msg
, GTK_STOCK_CANCEL
,
5636 g_strconcat("+", _("_Send"), NULL
), NULL
, FALSE
,
5637 NULL
, ALERT_ERROR
, G_ALERTDEFAULT
);
5640 if (aval
!= G_ALERTALTERNATE
) {
5645 out_codeset
= src_codeset
;
5651 out_codeset
= src_codeset
;
5656 if (encoding
== ENC_8BIT
|| encoding
== ENC_7BIT
) {
5657 if (!strncmp(buf
, "From ", sizeof("From ")-1) ||
5658 strstr(buf
, "\nFrom ") != NULL
) {
5659 encoding
= ENC_QUOTED_PRINTABLE
;
5663 mimetext
= procmime_mimeinfo_new();
5664 mimetext
->content
= MIMECONTENT_MEM
;
5665 mimetext
->tmp
= TRUE
; /* must free content later */
5666 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5667 * and free the data, which we need later. */
5668 mimetext
->data
.mem
= g_strdup(buf
);
5669 mimetext
->type
= MIMETYPE_TEXT
;
5670 mimetext
->subtype
= g_strdup("plain");
5671 g_hash_table_insert(mimetext
->typeparameters
, g_strdup("charset"),
5672 g_strdup(out_codeset
));
5674 /* protect trailing spaces when signing message */
5675 if (action
== COMPOSE_WRITE_FOR_SEND
&& compose
->use_signing
&&
5676 privacy_system_can_sign(compose
->privacy_system
)) {
5677 encoding
= ENC_QUOTED_PRINTABLE
;
5680 debug_print("main text: %zd bytes encoded as %s in %d\n",
5681 strlen(buf
), out_codeset
, encoding
);
5683 /* check for line length limit */
5684 if (action
== COMPOSE_WRITE_FOR_SEND
&&
5685 encoding
!= ENC_QUOTED_PRINTABLE
&& encoding
!= ENC_BASE64
&&
5686 check_line_length(buf
, 1000, &line
) < 0) {
5689 msg
= g_strdup_printf
5690 (_("Line %d exceeds the line length limit (998 bytes).\n"
5691 "The contents of the message might be broken on the way to the delivery.\n"
5693 "Send it anyway?"), line
+ 1);
5694 aval
= alertpanel(_("Warning"), msg
, GTK_STOCK_CANCEL
, GTK_STOCK_OK
, NULL
);
5696 if (aval
!= G_ALERTALTERNATE
) {
5702 if (encoding
!= ENC_UNKNOWN
)
5703 procmime_encode_content(mimetext
, encoding
);
5705 /* append attachment parts */
5706 if (compose_use_attach(compose
) && attach_parts
) {
5707 MimeInfo
*mimempart
;
5708 gchar
*boundary
= NULL
;
5709 mimempart
= procmime_mimeinfo_new();
5710 mimempart
->content
= MIMECONTENT_EMPTY
;
5711 mimempart
->type
= MIMETYPE_MULTIPART
;
5712 mimempart
->subtype
= g_strdup("mixed");
5716 boundary
= generate_mime_boundary(NULL
);
5717 } while (strstr(buf
, boundary
) != NULL
);
5719 g_hash_table_insert(mimempart
->typeparameters
, g_strdup("boundary"),
5722 mimetext
->disposition
= DISPOSITIONTYPE_INLINE
;
5724 g_node_append(mimempart
->node
, mimetext
->node
);
5725 g_node_append(mimemsg
->node
, mimempart
->node
);
5727 if (compose_add_attachments(compose
, mimempart
) < 0)
5730 g_node_append(mimemsg
->node
, mimetext
->node
);
5734 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
))) != 0) {
5735 gchar
*spec
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
5736 /* extract name and address */
5737 if (strstr(spec
, " <") && strstr(spec
, ">")) {
5738 from_addr
= g_strdup(strrchr(spec
, '<')+1);
5739 *(strrchr(from_addr
, '>')) = '\0';
5740 from_name
= g_strdup(spec
);
5741 *(strrchr(from_name
, '<')) = '\0';
5748 /* sign message if sending */
5749 if (action
== COMPOSE_WRITE_FOR_SEND
&& compose
->use_signing
&&
5750 privacy_system_can_sign(compose
->privacy_system
))
5751 if (!privacy_sign(compose
->privacy_system
, mimemsg
,
5752 compose
->account
, from_addr
)) {
5760 if (compose
->use_encryption
) {
5761 if (compose
->encdata
!= NULL
&&
5762 strcmp(compose
->encdata
, "_DONT_ENCRYPT_")) {
5764 /* First, write an unencrypted copy and save it to outbox, if
5765 * user wants that. */
5766 if (compose
->account
->save_encrypted_as_clear_text
) {
5767 debug_print("saving sent message unencrypted...\n");
5768 FILE *tmpfp
= get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file
);
5772 /* fp now points to a file with headers written,
5773 * let's make a copy. */
5775 content
= file_read_stream_to_str(fp
);
5777 str_write_to_file(content
, tmp_enc_file
);
5780 /* Now write the unencrypted body. */
5781 if ((tmpfp
= g_fopen(tmp_enc_file
, "a")) != NULL
) {
5782 procmime_write_mimeinfo(mimemsg
, tmpfp
);
5785 outbox
= folder_find_item_from_identifier(compose_get_save_to(compose
));
5787 outbox
= folder_get_default_outbox();
5789 procmsg_save_to_outbox(outbox
, tmp_enc_file
, TRUE
);
5790 claws_unlink(tmp_enc_file
);
5792 g_warning("Can't open file '%s'", tmp_enc_file
);
5795 g_warning("couldn't get tempfile");
5798 if (!privacy_encrypt(compose
->privacy_system
, mimemsg
, compose
->encdata
)) {
5799 debug_print("Couldn't encrypt mime structure: %s.\n",
5800 privacy_get_error());
5801 alertpanel_error(_("Couldn't encrypt the email: %s"),
5802 privacy_get_error());
5807 procmime_write_mimeinfo(mimemsg
, fp
);
5809 procmime_mimeinfo_free_all(&mimemsg
);
5814 static gint
compose_write_body_to_file(Compose
*compose
, const gchar
*file
)
5816 GtkTextBuffer
*buffer
;
5817 GtkTextIter start
, end
;
5822 if ((fp
= g_fopen(file
, "wb")) == NULL
) {
5823 FILE_OP_ERROR(file
, "fopen");
5827 /* chmod for security */
5828 if (change_file_mode_rw(fp
, file
) < 0) {
5829 FILE_OP_ERROR(file
, "chmod");
5830 g_warning("can't change file mode");
5833 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
5834 gtk_text_buffer_get_start_iter(buffer
, &start
);
5835 gtk_text_buffer_get_end_iter(buffer
, &end
);
5836 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
5838 chars
= conv_codeset_strdup
5839 (tmp
, CS_INTERNAL
, conv_get_locale_charset_str());
5848 len
= strlen(chars
);
5849 if (fwrite(chars
, sizeof(gchar
), len
, fp
) != len
) {
5850 FILE_OP_ERROR(file
, "fwrite");
5859 if (fclose(fp
) == EOF
) {
5860 FILE_OP_ERROR(file
, "fclose");
5867 static gint
compose_remove_reedit_target(Compose
*compose
, gboolean force
)
5870 MsgInfo
*msginfo
= compose
->targetinfo
;
5872 cm_return_val_if_fail(compose
->mode
== COMPOSE_REEDIT
, -1);
5873 if (!msginfo
) return -1;
5875 if (!force
&& MSG_IS_LOCKED(msginfo
->flags
))
5878 item
= msginfo
->folder
;
5879 cm_return_val_if_fail(item
!= NULL
, -1);
5881 if (procmsg_msg_exist(msginfo
) &&
5882 (folder_has_parent_of_type(item
, F_QUEUE
) ||
5883 folder_has_parent_of_type(item
, F_DRAFT
)
5884 || msginfo
== compose
->autosaved_draft
)) {
5885 if (folder_item_remove_msg(item
, msginfo
->msgnum
) < 0) {
5886 g_warning("can't remove the old message");
5889 debug_print("removed reedit target %d\n", msginfo
->msgnum
);
5896 static void compose_remove_draft(Compose
*compose
)
5899 MsgInfo
*msginfo
= compose
->targetinfo
;
5900 drafts
= account_get_special_folder(compose
->account
, F_DRAFT
);
5902 if (procmsg_msg_exist(msginfo
)) {
5903 folder_item_remove_msg(drafts
, msginfo
->msgnum
);
5908 gint
compose_queue(Compose
*compose
, gint
*msgnum
, FolderItem
**item
, gchar
**msgpath
,
5909 gboolean remove_reedit_target
)
5911 return compose_queue_sub (compose
, msgnum
, item
, msgpath
, FALSE
, remove_reedit_target
);
5914 static gboolean
compose_warn_encryption(Compose
*compose
)
5916 const gchar
*warning
= privacy_get_encrypt_warning(compose
->privacy_system
);
5917 AlertValue val
= G_ALERTALTERNATE
;
5919 if (warning
== NULL
)
5922 val
= alertpanel_full(_("Encryption warning"), warning
,
5923 GTK_STOCK_CANCEL
, g_strconcat("+", _("C_ontinue"), NULL
), NULL
,
5924 TRUE
, NULL
, ALERT_WARNING
, G_ALERTALTERNATE
);
5925 if (val
& G_ALERTDISABLE
) {
5926 val
&= ~G_ALERTDISABLE
;
5927 if (val
== G_ALERTALTERNATE
)
5928 privacy_inhibit_encrypt_warning(compose
->privacy_system
,
5932 if (val
== G_ALERTALTERNATE
) {
5939 static gint
compose_queue_sub(Compose
*compose
, gint
*msgnum
, FolderItem
**item
,
5940 gchar
**msgpath
, gboolean check_subject
,
5941 gboolean remove_reedit_target
)
5948 PrefsAccount
*mailac
= NULL
, *newsac
= NULL
;
5949 gboolean err
= FALSE
;
5951 debug_print("queueing message...\n");
5952 cm_return_val_if_fail(compose
->account
!= NULL
, -1);
5954 if (compose_check_entries(compose
, check_subject
) == FALSE
) {
5955 if (compose
->batch
) {
5956 gtk_widget_show_all(compose
->window
);
5961 if (!compose
->to_list
&& !compose
->newsgroup_list
) {
5962 g_warning("can't get recipient list.");
5966 if (compose
->to_list
) {
5967 if (compose
->account
->protocol
!= A_NNTP
)
5968 mailac
= compose
->account
;
5969 else if (cur_account
&& cur_account
->protocol
!= A_NNTP
)
5970 mailac
= cur_account
;
5971 else if (!(mailac
= compose_current_mail_account())) {
5972 alertpanel_error(_("No account for sending mails available!"));
5977 if (compose
->newsgroup_list
) {
5978 if (compose
->account
->protocol
== A_NNTP
)
5979 newsac
= compose
->account
;
5981 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5986 /* write queue header */
5987 tmp
= g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5988 G_DIR_SEPARATOR
, compose
, (guint
) rand());
5989 debug_print("queuing to %s\n", tmp
);
5990 if ((fp
= g_fopen(tmp
, "w+b")) == NULL
) {
5991 FILE_OP_ERROR(tmp
, "fopen");
5996 if (change_file_mode_rw(fp
, tmp
) < 0) {
5997 FILE_OP_ERROR(tmp
, "chmod");
5998 g_warning("can't change file mode");
6001 /* queueing variables */
6002 err
|= (fprintf(fp
, "AF:\n") < 0);
6003 err
|= (fprintf(fp
, "NF:0\n") < 0);
6004 err
|= (fprintf(fp
, "PS:10\n") < 0);
6005 err
|= (fprintf(fp
, "SRH:1\n") < 0);
6006 err
|= (fprintf(fp
, "SFN:\n") < 0);
6007 err
|= (fprintf(fp
, "DSR:\n") < 0);
6009 err
|= (fprintf(fp
, "MID:<%s>\n", compose
->msgid
) < 0);
6011 err
|= (fprintf(fp
, "MID:\n") < 0);
6012 err
|= (fprintf(fp
, "CFG:\n") < 0);
6013 err
|= (fprintf(fp
, "PT:0\n") < 0);
6014 err
|= (fprintf(fp
, "S:%s\n", compose
->account
->address
) < 0);
6015 err
|= (fprintf(fp
, "RQ:\n") < 0);
6017 err
|= (fprintf(fp
, "SSV:%s\n", mailac
->smtp_server
) < 0);
6019 err
|= (fprintf(fp
, "SSV:\n") < 0);
6021 err
|= (fprintf(fp
, "NSV:%s\n", newsac
->nntp_server
) < 0);
6023 err
|= (fprintf(fp
, "NSV:\n") < 0);
6024 err
|= (fprintf(fp
, "SSH:\n") < 0);
6025 /* write recepient list */
6026 if (compose
->to_list
) {
6027 err
|= (fprintf(fp
, "R:<%s>", (gchar
*)compose
->to_list
->data
) < 0);
6028 for (cur
= compose
->to_list
->next
; cur
!= NULL
;
6030 err
|= (fprintf(fp
, ",<%s>", (gchar
*)cur
->data
) < 0);
6031 err
|= (fprintf(fp
, "\n") < 0);
6033 /* write newsgroup list */
6034 if (compose
->newsgroup_list
) {
6035 err
|= (fprintf(fp
, "NG:") < 0);
6036 err
|= (fprintf(fp
, "%s", (gchar
*)compose
->newsgroup_list
->data
) < 0);
6037 for (cur
= compose
->newsgroup_list
->next
; cur
!= NULL
; cur
= cur
->next
)
6038 err
|= (fprintf(fp
, ",%s", (gchar
*)cur
->data
) < 0);
6039 err
|= (fprintf(fp
, "\n") < 0);
6041 /* Sylpheed account IDs */
6043 err
|= (fprintf(fp
, "MAID:%d\n", mailac
->account_id
) < 0);
6045 err
|= (fprintf(fp
, "NAID:%d\n", newsac
->account_id
) < 0);
6048 if (compose
->privacy_system
!= NULL
) {
6049 err
|= (fprintf(fp
, "X-Claws-Privacy-System:%s\n", compose
->privacy_system
) < 0);
6050 err
|= (fprintf(fp
, "X-Claws-Sign:%d\n", compose
->use_signing
) < 0);
6051 if (compose
->use_encryption
) {
6052 if (!compose_warn_encryption(compose
)) {
6058 if (mailac
&& mailac
->encrypt_to_self
) {
6059 GSList
*tmp_list
= g_slist_copy(compose
->to_list
);
6060 tmp_list
= g_slist_append(tmp_list
, compose
->account
->address
);
6061 compose
->encdata
= privacy_get_encrypt_data(compose
->privacy_system
, tmp_list
);
6062 g_slist_free(tmp_list
);
6064 compose
->encdata
= privacy_get_encrypt_data(compose
->privacy_system
, compose
->to_list
);
6066 if (compose
->encdata
!= NULL
) {
6067 if (strcmp(compose
->encdata
, "_DONT_ENCRYPT_")) {
6068 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
6069 err
|= (fprintf(fp
, "X-Claws-Encrypt-Data:%s\n",
6070 compose
->encdata
) < 0);
6071 } /* else we finally dont want to encrypt */
6073 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
6074 /* and if encdata was null, it means there's been a problem in
6077 g_warning("failed to write queue message");
6086 /* Save copy folder */
6087 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
))) {
6088 gchar
*savefolderid
;
6090 savefolderid
= compose_get_save_to(compose
);
6091 err
|= (fprintf(fp
, "SCF:%s\n", savefolderid
) < 0);
6092 g_free(savefolderid
);
6094 /* Save copy folder */
6095 if (compose
->return_receipt
) {
6096 err
|= (fprintf(fp
, "RRCPT:1\n") < 0);
6098 /* Message-ID of message replying to */
6099 if ((compose
->replyinfo
!= NULL
) && (compose
->replyinfo
->msgid
!= NULL
)) {
6100 gchar
*folderid
= NULL
;
6102 if (compose
->replyinfo
->folder
)
6103 folderid
= folder_item_get_identifier(compose
->replyinfo
->folder
);
6104 if (folderid
== NULL
)
6105 folderid
= g_strdup("NULL");
6107 err
|= (fprintf(fp
, "RMID:%s\t%d\t%s\n", folderid
, compose
->replyinfo
->msgnum
, compose
->replyinfo
->msgid
) < 0);
6110 /* Message-ID of message forwarding to */
6111 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
6112 gchar
*folderid
= NULL
;
6114 if (compose
->fwdinfo
->folder
)
6115 folderid
= folder_item_get_identifier(compose
->fwdinfo
->folder
);
6116 if (folderid
== NULL
)
6117 folderid
= g_strdup("NULL");
6119 err
|= (fprintf(fp
, "FMID:%s\t%d\t%s\n", folderid
, compose
->fwdinfo
->msgnum
, compose
->fwdinfo
->msgid
) < 0);
6123 err
|= (fprintf(fp
, "X-Claws-Auto-Wrapping:%d\n", compose
->autowrap
) < 0);
6124 err
|= (fprintf(fp
, "X-Claws-Auto-Indent:%d\n", compose
->autoindent
) < 0);
6126 /* end of headers */
6127 err
|= (fprintf(fp
, "X-Claws-End-Special-Headers: 1\n") < 0);
6129 if (compose
->redirect_filename
!= NULL
) {
6130 if (compose_redirect_write_to_file(compose
, fp
) < 0) {
6138 if ((result
= compose_write_to_file(compose
, fp
, COMPOSE_WRITE_FOR_SEND
, TRUE
)) < 0) {
6142 return result
- 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
6146 g_warning("failed to write queue message");
6152 if (fclose(fp
) == EOF
) {
6153 FILE_OP_ERROR(tmp
, "fclose");
6159 if (item
&& *item
) {
6162 queue
= account_get_special_folder(compose
->account
, F_QUEUE
);
6165 g_warning("can't find queue folder");
6170 folder_item_scan(queue
);
6171 if ((num
= folder_item_add_msg(queue
, tmp
, NULL
, FALSE
)) < 0) {
6172 g_warning("can't queue the message");
6178 if (msgpath
== NULL
) {
6184 if (compose
->mode
== COMPOSE_REEDIT
&& remove_reedit_target
) {
6185 compose_remove_reedit_target(compose
, FALSE
);
6188 if ((msgnum
!= NULL
) && (item
!= NULL
)) {
6196 static int compose_add_attachments(Compose
*compose
, MimeInfo
*parent
)
6199 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
6202 gchar
*type
, *subtype
;
6203 GtkTreeModel
*model
;
6206 model
= gtk_tree_view_get_model(tree_view
);
6208 if (!gtk_tree_model_get_iter_first(model
, &iter
))
6211 gtk_tree_model_get(model
, &iter
,
6215 if (!is_file_exist(ainfo
->file
)) {
6216 gchar
*msg
= g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo
->file
);
6217 AlertValue val
= alertpanel_full(_("Warning"), msg
, _("Cancel sending"), _("Ignore attachment"),
6218 NULL
, FALSE
, NULL
, ALERT_WARNING
, G_ALERTDEFAULT
);
6220 if (val
== G_ALERTDEFAULT
) {
6225 if (g_stat(ainfo
->file
, &statbuf
) < 0)
6228 mimepart
= procmime_mimeinfo_new();
6229 mimepart
->content
= MIMECONTENT_FILE
;
6230 mimepart
->data
.filename
= g_strdup(ainfo
->file
);
6231 mimepart
->tmp
= FALSE
; /* or we destroy our attachment */
6232 mimepart
->offset
= 0;
6233 mimepart
->length
= statbuf
.st_size
;
6235 type
= g_strdup(ainfo
->content_type
);
6237 if (!strchr(type
, '/')) {
6239 type
= g_strdup("application/octet-stream");
6242 subtype
= strchr(type
, '/') + 1;
6243 *(subtype
- 1) = '\0';
6244 mimepart
->type
= procmime_get_media_type(type
);
6245 mimepart
->subtype
= g_strdup(subtype
);
6248 if (mimepart
->type
== MIMETYPE_MESSAGE
&&
6249 !g_ascii_strcasecmp(mimepart
->subtype
, "rfc822")) {
6250 mimepart
->disposition
= DISPOSITIONTYPE_INLINE
;
6251 } else if (mimepart
->type
== MIMETYPE_TEXT
) {
6252 if (!ainfo
->name
&& g_ascii_strcasecmp(mimepart
->subtype
, "plain")) {
6253 /* Text parts with no name come from multipart/alternative
6254 * forwards. Make sure the recipient won't look at the
6255 * original HTML part by mistake. */
6256 mimepart
->disposition
= DISPOSITIONTYPE_ATTACHMENT
;
6257 ainfo
->name
= g_strdup_printf(_("Original %s part"),
6261 g_hash_table_insert(mimepart
->typeparameters
,
6262 g_strdup("charset"), g_strdup(ainfo
->charset
));
6264 if (ainfo
->name
&& mimepart
->type
!= MIMETYPE_MESSAGE
) {
6265 if (mimepart
->type
== MIMETYPE_APPLICATION
&&
6266 !strcmp2(mimepart
->subtype
, "octet-stream"))
6267 g_hash_table_insert(mimepart
->typeparameters
,
6268 g_strdup("name"), g_strdup(ainfo
->name
));
6269 g_hash_table_insert(mimepart
->dispositionparameters
,
6270 g_strdup("filename"), g_strdup(ainfo
->name
));
6271 mimepart
->disposition
= DISPOSITIONTYPE_ATTACHMENT
;
6274 if (mimepart
->type
== MIMETYPE_MESSAGE
6275 || mimepart
->type
== MIMETYPE_MULTIPART
)
6276 ainfo
->encoding
= ENC_BINARY
;
6277 else if (compose
->use_signing
) {
6278 if (ainfo
->encoding
== ENC_7BIT
)
6279 ainfo
->encoding
= ENC_QUOTED_PRINTABLE
;
6280 else if (ainfo
->encoding
== ENC_8BIT
)
6281 ainfo
->encoding
= ENC_BASE64
;
6286 procmime_encode_content(mimepart
, ainfo
->encoding
);
6288 g_node_append(parent
->node
, mimepart
->node
);
6289 } while (gtk_tree_model_iter_next(model
, &iter
));
6294 static gchar
*compose_quote_list_of_addresses(gchar
*str
)
6296 GSList
*list
= NULL
, *item
= NULL
;
6297 gchar
*qname
= NULL
, *faddr
= NULL
, *result
= NULL
;
6299 list
= address_list_append_with_comments(list
, str
);
6300 for (item
= list
; item
!= NULL
; item
= item
->next
) {
6301 gchar
*spec
= item
->data
;
6302 gchar
*endofname
= strstr(spec
, " <");
6303 if (endofname
!= NULL
) {
6306 QUOTE_IF_REQUIRED_NORMAL(qname
, spec
, return NULL
);
6307 qqname
= escape_internal_quotes(qname
, '"');
6309 if (*qname
!= *spec
|| qqname
!= qname
) { /* has been quoted, compute new */
6310 gchar
*addr
= g_strdup(endofname
);
6311 gchar
*name
= (qqname
!= qname
)? qqname
: g_strdup(qname
);
6312 faddr
= g_strconcat(name
, addr
, NULL
);
6315 debug_print("new auto-quoted address: '%s'\n", faddr
);
6319 result
= g_strdup((faddr
!= NULL
)? faddr
: spec
);
6321 result
= g_strconcat(result
,
6323 (faddr
!= NULL
)? faddr
: spec
,
6326 if (faddr
!= NULL
) {
6331 slist_free_strings_full(list
);
6336 #define IS_IN_CUSTOM_HEADER(header) \
6337 (compose->account->add_customhdr && \
6338 custom_header_find(compose->account->customhdr_list, header) != NULL)
6340 static void compose_add_headerfield_from_headerlist(Compose
*compose
,
6342 const gchar
*fieldname
,
6343 const gchar
*seperator
)
6345 gchar
*str
, *fieldname_w_colon
;
6346 gboolean add_field
= FALSE
;
6348 ComposeHeaderEntry
*headerentry
;
6349 const gchar
*headerentryname
;
6350 const gchar
*trans_fieldname
;
6353 if (IS_IN_CUSTOM_HEADER(fieldname
))
6356 debug_print("Adding %s-fields\n", fieldname
);
6358 fieldstr
= g_string_sized_new(64);
6360 fieldname_w_colon
= g_strconcat(fieldname
, ":", NULL
);
6361 trans_fieldname
= prefs_common_translated_header_name(fieldname_w_colon
);
6363 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6364 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6365 headerentryname
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
)))));
6367 if (!g_utf8_collate(trans_fieldname
, headerentryname
)) {
6368 gchar
* ustr
= gtk_editable_get_chars(GTK_EDITABLE(headerentry
->entry
), 0, -1);
6370 str
= compose_quote_list_of_addresses(ustr
);
6372 if (str
!= NULL
&& str
[0] != '\0') {
6374 g_string_append(fieldstr
, seperator
);
6375 g_string_append(fieldstr
, str
);
6384 buf
= g_new0(gchar
, fieldstr
->len
* 4 + 256);
6385 compose_convert_header
6386 (compose
, buf
, fieldstr
->len
* 4 + 256, fieldstr
->str
,
6387 strlen(fieldname
) + 2, TRUE
);
6388 g_string_append_printf(header
, "%s: %s\n", fieldname
, buf
);
6392 g_free(fieldname_w_colon
);
6393 g_string_free(fieldstr
, TRUE
);
6398 static gchar
*compose_get_manual_headers_info(Compose
*compose
)
6400 GString
*sh_header
= g_string_new(" ");
6402 gchar
*std_headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
6404 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6405 ComposeHeaderEntry
*headerentry
;
6408 gchar
*headername_wcolon
;
6409 const gchar
*headername_trans
;
6411 gboolean standard_header
= FALSE
;
6413 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6415 tmp
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
))))));
6417 if (*tmp
== '\0' || strchr(tmp
, ' ') != NULL
|| strchr(tmp
, '\r') != NULL
|| strchr(tmp
, '\n') != NULL
) {
6422 if (!strstr(tmp
, ":")) {
6423 headername_wcolon
= g_strconcat(tmp
, ":", NULL
);
6424 headername
= g_strdup(tmp
);
6426 headername_wcolon
= g_strdup(tmp
);
6427 headername
= g_strdup(strtok(tmp
, ":"));
6431 string
= std_headers
;
6432 while (*string
!= NULL
) {
6433 headername_trans
= prefs_common_translated_header_name(*string
);
6434 if (!strcmp(headername_trans
, headername_wcolon
))
6435 standard_header
= TRUE
;
6438 if (!standard_header
&& !IS_IN_CUSTOM_HEADER(headername
))
6439 g_string_append_printf(sh_header
, "%s ", headername
);
6441 g_free(headername_wcolon
);
6443 g_string_truncate(sh_header
, strlen(sh_header
->str
) - 1); /* remove last space */
6444 return g_string_free(sh_header
, FALSE
);
6447 static gchar
*compose_get_header(Compose
*compose
)
6449 gchar buf
[BUFFSIZE
];
6450 const gchar
*entry_str
;
6454 gchar
*std_headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
6456 gchar
*from_name
= NULL
, *from_address
= NULL
;
6459 cm_return_val_if_fail(compose
->account
!= NULL
, NULL
);
6460 cm_return_val_if_fail(compose
->account
->address
!= NULL
, NULL
);
6462 header
= g_string_sized_new(64);
6465 if (prefs_common
.hide_timezone
)
6466 get_rfc822_date_hide_tz(buf
, sizeof(buf
));
6468 get_rfc822_date(buf
, sizeof(buf
));
6469 g_string_append_printf(header
, "Date: %s\n", buf
);
6473 if (compose
->account
->name
&& *compose
->account
->name
) {
6475 QUOTE_IF_REQUIRED(buf
, compose
->account
->name
);
6476 tmp
= g_strdup_printf("%s <%s>",
6477 buf
, compose
->account
->address
);
6479 tmp
= g_strdup_printf("%s",
6480 compose
->account
->address
);
6482 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
)), tmp
)
6483 || strlen(gtk_entry_get_text(GTK_ENTRY(compose
->from_name
))) == 0) {
6485 from_name
= compose
->account
->name
? g_strdup(compose
->account
->name
):NULL
;
6486 from_address
= g_strdup(compose
->account
->address
);
6488 gchar
*spec
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
6489 /* extract name and address */
6490 if (strstr(spec
, " <") && strstr(spec
, ">")) {
6491 from_address
= g_strdup(strrchr(spec
, '<')+1);
6492 *(strrchr(from_address
, '>')) = '\0';
6493 from_name
= g_strdup(spec
);
6494 *(strrchr(from_name
, '<')) = '\0';
6497 from_address
= g_strdup(spec
);
6504 if (from_name
&& *from_name
) {
6506 compose_convert_header
6507 (compose
, buf
, sizeof(buf
), from_name
,
6508 strlen("From: "), TRUE
);
6509 QUOTE_IF_REQUIRED(name
, buf
);
6510 qname
= escape_internal_quotes(name
, '"');
6512 g_string_append_printf(header
, "From: %s <%s>\n",
6513 qname
, from_address
);
6514 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6515 compose
->return_receipt
) {
6516 compose_convert_header(compose
, buf
, sizeof(buf
), from_name
,
6517 strlen("Disposition-Notification-To: "),
6519 g_string_append_printf(header
, "Disposition-Notification-To: %s <%s>\n", buf
, from_address
);
6524 g_string_append_printf(header
, "From: %s\n", from_address
);
6525 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To") &&
6526 compose
->return_receipt
)
6527 g_string_append_printf(header
, "Disposition-Notification-To: %s\n", from_address
);
6531 g_free(from_address
);
6534 compose_add_headerfield_from_headerlist(compose
, header
, "To", ", ");
6537 compose_add_headerfield_from_headerlist(compose
, header
, "Newsgroups", ",");
6540 compose_add_headerfield_from_headerlist(compose
, header
, "Cc", ", ");
6544 * If this account is a NNTP account remove Bcc header from
6545 * message body since it otherwise will be publicly shown
6547 if (compose
->account
->protocol
!= A_NNTP
)
6548 compose_add_headerfield_from_headerlist(compose
, header
, "Bcc", ", ");
6551 str
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
6553 if (*str
!= '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6556 compose_convert_header(compose
, buf
, sizeof(buf
), str
,
6557 strlen("Subject: "), FALSE
);
6558 g_string_append_printf(header
, "Subject: %s\n", buf
);
6564 if (compose
->account
->set_domain
&& compose
->account
->domain
) {
6565 g_snprintf(buf
, sizeof(buf
), "%s", compose
->account
->domain
);
6566 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6567 g_snprintf(buf
, sizeof(buf
), "%s",
6568 strchr(compose
->account
->address
, '@') ?
6569 strchr(compose
->account
->address
, '@')+1 :
6570 compose
->account
->address
);
6572 g_snprintf(buf
, sizeof(buf
), "%s", "");
6575 if (compose
->account
->gen_msgid
) {
6577 if (compose
->account
->msgid_with_addr
) {
6578 addr
= compose
->account
->address
;
6580 generate_msgid(buf
, sizeof(buf
), addr
);
6581 g_string_append_printf(header
, "Message-ID: <%s>\n", buf
);
6583 g_free(compose
->msgid
);
6584 compose
->msgid
= g_strdup(buf
);
6586 compose
->msgid
= NULL
;
6589 if (compose
->remove_references
== FALSE
) {
6591 if (compose
->inreplyto
&& compose
->to_list
)
6592 g_string_append_printf(header
, "In-Reply-To: <%s>\n", compose
->inreplyto
);
6595 if (compose
->references
)
6596 g_string_append_printf(header
, "References: %s\n", compose
->references
);
6600 compose_add_headerfield_from_headerlist(compose
, header
, "Followup-To", ",");
6603 compose_add_headerfield_from_headerlist(compose
, header
, "Reply-To", ", ");
6606 if (compose
->account
->organization
&&
6607 strlen(compose
->account
->organization
) &&
6608 !IS_IN_CUSTOM_HEADER("Organization")) {
6609 compose_convert_header(compose
, buf
, sizeof(buf
),
6610 compose
->account
->organization
,
6611 strlen("Organization: "), FALSE
);
6612 g_string_append_printf(header
, "Organization: %s\n", buf
);
6615 /* Program version and system info */
6616 if (compose
->account
->gen_xmailer
&&
6617 g_slist_length(compose
->to_list
) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6618 !compose
->newsgroup_list
) {
6619 g_string_append_printf(header
, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6621 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
6624 if (compose
->account
->gen_xmailer
&&
6625 g_slist_length(compose
->newsgroup_list
) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6626 g_string_append_printf(header
, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6628 gtk_major_version
, gtk_minor_version
, gtk_micro_version
,
6632 /* custom headers */
6633 if (compose
->account
->add_customhdr
) {
6636 for (cur
= compose
->account
->customhdr_list
; cur
!= NULL
;
6638 CustomHeader
*chdr
= (CustomHeader
*)cur
->data
;
6640 if (custom_header_is_allowed(chdr
->name
)
6641 && chdr
->value
!= NULL
6642 && *(chdr
->value
) != '\0') {
6643 compose_convert_header
6644 (compose
, buf
, sizeof(buf
),
6646 strlen(chdr
->name
) + 2, FALSE
);
6647 g_string_append_printf(header
, "%s: %s\n", chdr
->name
, buf
);
6652 /* Automatic Faces and X-Faces */
6653 if (get_account_xface (buf
, sizeof(buf
), compose
->account
->account_name
) == 0) {
6654 g_string_append_printf(header
, "X-Face: %s\n", buf
);
6656 else if (get_default_xface (buf
, sizeof(buf
)) == 0) {
6657 g_string_append_printf(header
, "X-Face: %s\n", buf
);
6659 if (get_account_face (buf
, sizeof(buf
), compose
->account
->account_name
) == 0) {
6660 g_string_append_printf(header
, "Face: %s\n", buf
);
6662 else if (get_default_face (buf
, sizeof(buf
)) == 0) {
6663 g_string_append_printf(header
, "Face: %s\n", buf
);
6667 switch (compose
->priority
) {
6668 case PRIORITY_HIGHEST
: g_string_append_printf(header
, "Importance: high\n"
6669 "X-Priority: 1 (Highest)\n");
6671 case PRIORITY_HIGH
: g_string_append_printf(header
, "Importance: high\n"
6672 "X-Priority: 2 (High)\n");
6674 case PRIORITY_NORMAL
: break;
6675 case PRIORITY_LOW
: g_string_append_printf(header
, "Importance: low\n"
6676 "X-Priority: 4 (Low)\n");
6678 case PRIORITY_LOWEST
: g_string_append_printf(header
, "Importance: low\n"
6679 "X-Priority: 5 (Lowest)\n");
6681 default: debug_print("compose: priority unknown : %d\n",
6685 /* get special headers */
6686 for (list
= compose
->header_list
; list
; list
= list
->next
) {
6687 ComposeHeaderEntry
*headerentry
;
6690 gchar
*headername_wcolon
;
6691 const gchar
*headername_trans
;
6694 gboolean standard_header
= FALSE
;
6696 headerentry
= ((ComposeHeaderEntry
*)list
->data
);
6698 tmp
= g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry
->combo
))))));
6700 if (*tmp
== '\0' || strchr(tmp
, ' ') != NULL
|| strchr(tmp
, '\r') != NULL
|| strchr(tmp
, '\n') != NULL
) {
6705 if (!strstr(tmp
, ":")) {
6706 headername_wcolon
= g_strconcat(tmp
, ":", NULL
);
6707 headername
= g_strdup(tmp
);
6709 headername_wcolon
= g_strdup(tmp
);
6710 headername
= g_strdup(strtok(tmp
, ":"));
6714 entry_str
= gtk_entry_get_text(GTK_ENTRY(headerentry
->entry
));
6715 Xstrdup_a(headervalue
, entry_str
, return NULL
);
6716 subst_char(headervalue
, '\r', ' ');
6717 subst_char(headervalue
, '\n', ' ');
6718 string
= std_headers
;
6719 while (*string
!= NULL
) {
6720 headername_trans
= prefs_common_translated_header_name(*string
);
6721 if (!strcmp(headername_trans
, headername_wcolon
))
6722 standard_header
= TRUE
;
6725 if (!standard_header
&& !IS_IN_CUSTOM_HEADER(headername
))
6726 g_string_append_printf(header
, "%s %s\n", headername_wcolon
, headervalue
);
6729 g_free(headername_wcolon
);
6733 g_string_free(header
, FALSE
);
6738 #undef IS_IN_CUSTOM_HEADER
6740 static void compose_convert_header(Compose
*compose
, gchar
*dest
, gint len
, gchar
*src
,
6741 gint header_len
, gboolean addr_field
)
6743 gchar
*tmpstr
= NULL
;
6744 const gchar
*out_codeset
= NULL
;
6746 cm_return_if_fail(src
!= NULL
);
6747 cm_return_if_fail(dest
!= NULL
);
6749 if (len
< 1) return;
6751 tmpstr
= g_strdup(src
);
6753 subst_char(tmpstr
, '\n', ' ');
6754 subst_char(tmpstr
, '\r', ' ');
6757 if (!g_utf8_validate(tmpstr
, -1, NULL
)) {
6758 gchar
*mybuf
= g_malloc(strlen(tmpstr
)*2 +1);
6759 conv_localetodisp(mybuf
, strlen(tmpstr
)*2 +1, tmpstr
);
6764 codeconv_set_strict(TRUE
);
6765 conv_encode_header_full(dest
, len
, tmpstr
, header_len
, addr_field
,
6766 conv_get_charset_str(compose
->out_encoding
));
6767 codeconv_set_strict(FALSE
);
6769 if (!dest
|| *dest
== '\0') {
6770 gchar
*test_conv_global_out
= NULL
;
6771 gchar
*test_conv_reply
= NULL
;
6773 /* automatic mode. be automatic. */
6774 codeconv_set_strict(TRUE
);
6776 out_codeset
= conv_get_outgoing_charset_str();
6778 debug_print("trying to convert to %s\n", out_codeset
);
6779 test_conv_global_out
= conv_codeset_strdup(src
, CS_INTERNAL
, out_codeset
);
6782 if (!test_conv_global_out
&& compose
->orig_charset
6783 && strcmp(compose
->orig_charset
, CS_US_ASCII
)) {
6784 out_codeset
= compose
->orig_charset
;
6785 debug_print("failure; trying to convert to %s\n", out_codeset
);
6786 test_conv_reply
= conv_codeset_strdup(src
, CS_INTERNAL
, out_codeset
);
6789 if (!test_conv_global_out
&& !test_conv_reply
) {
6791 out_codeset
= CS_INTERNAL
;
6792 debug_print("finally using %s\n", out_codeset
);
6794 g_free(test_conv_global_out
);
6795 g_free(test_conv_reply
);
6796 conv_encode_header_full(dest
, len
, tmpstr
, header_len
, addr_field
,
6798 codeconv_set_strict(FALSE
);
6803 static void compose_add_to_addressbook_cb(GtkMenuItem
*menuitem
, gpointer user_data
)
6807 cm_return_if_fail(user_data
!= NULL
);
6809 address
= g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data
)));
6810 g_strstrip(address
);
6811 if (*address
!= '\0') {
6812 gchar
*name
= procheader_get_fromname(address
);
6813 extract_address(address
);
6814 #ifndef USE_ALT_ADDRBOOK
6815 addressbook_add_contact(name
, address
, NULL
, NULL
);
6817 debug_print("%s: %s\n", name
, address
);
6818 if (addressadd_selection(name
, address
, NULL
, NULL
)) {
6819 debug_print( "addressbook_add_contact - added\n" );
6826 static void compose_entry_popup_extend(GtkEntry
*entry
, GtkMenu
*menu
, gpointer user_data
)
6828 GtkWidget
*menuitem
;
6831 cm_return_if_fail(menu
!= NULL
);
6832 cm_return_if_fail(GTK_IS_MENU_SHELL(menu
));
6834 menuitem
= gtk_separator_menu_item_new();
6835 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), menuitem
);
6836 gtk_widget_show(menuitem
);
6838 menuitem
= gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6839 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), menuitem
);
6841 address
= g_strdup(gtk_entry_get_text(GTK_ENTRY(entry
)));
6842 g_strstrip(address
);
6843 if (*address
== '\0') {
6844 gtk_widget_set_sensitive(GTK_WIDGET(menuitem
), FALSE
);
6847 g_signal_connect(G_OBJECT(menuitem
), "activate",
6848 G_CALLBACK(compose_add_to_addressbook_cb
), entry
);
6849 gtk_widget_show(menuitem
);
6852 void compose_add_extra_header(gchar
*header
, GtkListStore
*model
)
6855 if (strcmp(header
, "")) {
6856 COMBOBOX_ADD(model
, header
, COMPOSE_TO
);
6860 void compose_add_extra_header_entries(GtkListStore
*model
)
6864 gchar buf
[BUFFSIZE
];
6867 if (extra_headers
== NULL
) {
6868 exhrc
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, "extraheaderrc", NULL
);
6869 if ((exh
= g_fopen(exhrc
, "rb")) == NULL
) {
6870 debug_print("extra headers file not found\n");
6871 goto extra_headers_done
;
6873 while (fgets(buf
, BUFFSIZE
, exh
) != NULL
) {
6874 lastc
= strlen(buf
) - 1; /* remove trailing control chars */
6875 while (lastc
>= 0 && buf
[lastc
] != ':')
6876 buf
[lastc
--] = '\0';
6877 if (lastc
> 0 && buf
[0] != '#' && buf
[lastc
] == ':') {
6878 buf
[lastc
] = '\0'; /* remove trailing : for comparison */
6879 if (custom_header_is_allowed(buf
)) {
6881 extra_headers
= g_slist_prepend(extra_headers
, g_strdup(buf
));
6884 g_message("disallowed extra header line: %s\n", buf
);
6888 g_message("invalid extra header line: %s\n", buf
);
6894 extra_headers
= g_slist_prepend(extra_headers
, g_strdup("")); /* end of list */
6895 extra_headers
= g_slist_reverse(extra_headers
);
6897 g_slist_foreach(extra_headers
, (GFunc
)compose_add_extra_header
, (gpointer
)model
);
6900 static void compose_create_header_entry(Compose
*compose
)
6902 gchar
*headers
[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL
};
6909 const gchar
*header
= NULL
;
6910 ComposeHeaderEntry
*headerentry
;
6911 gboolean standard_header
= FALSE
;
6912 GtkListStore
*model
;
6915 headerentry
= g_new0(ComposeHeaderEntry
, 1);
6917 /* Combo box model */
6918 model
= gtk_list_store_new(3, G_TYPE_STRING
, G_TYPE_INT
, G_TYPE_BOOLEAN
);
6919 #if !GTK_CHECK_VERSION(2, 24, 0)
6920 combo
= gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model
), 0);
6922 COMBOBOX_ADD(model
, prefs_common_translated_header_name("To:"),
6924 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Cc:"),
6926 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Bcc:"),
6928 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Newsgroups:"),
6929 COMPOSE_NEWSGROUPS
);
6930 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Reply-To:"),
6932 COMBOBOX_ADD(model
, prefs_common_translated_header_name("Followup-To:"),
6933 COMPOSE_FOLLOWUPTO
);
6934 compose_add_extra_header_entries(model
);
6937 #if GTK_CHECK_VERSION(2, 24, 0)
6938 combo
= gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model
));
6939 GtkCellRenderer
*cell
= gtk_cell_renderer_text_new();
6940 gtk_cell_renderer_set_alignment(cell
, 0.0, 0.5);
6941 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo
), cell
, TRUE
);
6942 gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(combo
), 0);
6944 gtk_combo_box_set_active(GTK_COMBO_BOX(combo
), 0);
6945 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo
))), "grab_focus",
6946 G_CALLBACK(compose_grab_focus_cb
), compose
);
6947 gtk_widget_show(combo
);
6949 /* Putting only the combobox child into focus chain of its parent causes
6950 * the parent to be skipped when changing focus via Tab or Shift+Tab.
6951 * This eliminates need to pres Tab twice in order to really get from the
6952 * combobox to next widget. */
6954 l
= g_list_prepend(l
, gtk_bin_get_child(GTK_BIN(combo
)));
6955 gtk_container_set_focus_chain(GTK_CONTAINER(combo
), l
);
6958 gtk_table_attach(GTK_TABLE(compose
->header_table
), combo
, 0, 1,
6959 compose
->header_nextrow
, compose
->header_nextrow
+1,
6960 GTK_SHRINK
, GTK_FILL
, 0, 0);
6961 if (compose
->header_last
&& (compose
->draft_timeout_tag
!= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
)) {
6962 const gchar
*last_header_entry
= gtk_entry_get_text(
6963 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))));
6965 while (*string
!= NULL
) {
6966 if (!strcmp(prefs_common_translated_header_name(*string
), last_header_entry
))
6967 standard_header
= TRUE
;
6970 if (standard_header
)
6971 header
= gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))));
6973 if (!compose
->header_last
|| !standard_header
) {
6974 switch(compose
->account
->protocol
) {
6976 header
= prefs_common_translated_header_name("Newsgroups:");
6979 header
= prefs_common_translated_header_name("To:");
6984 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo
)))), header
);
6986 gtk_editable_set_editable(
6987 GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo
)))),
6988 prefs_common
.type_any_header
);
6990 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo
)))), "grab_focus",
6991 G_CALLBACK(compose_grab_focus_cb
), compose
);
6993 /* Entry field with cleanup button */
6994 button
= gtk_button_new();
6995 gtk_button_set_image(GTK_BUTTON(button
),
6996 gtk_image_new_from_stock(GTK_STOCK_CLEAR
, GTK_ICON_SIZE_MENU
));
6997 gtk_widget_show(button
);
6998 CLAWS_SET_TIP(button
,
6999 _("Delete entry contents"));
7000 entry
= gtk_entry_new();
7001 gtk_widget_show(entry
);
7002 CLAWS_SET_TIP(entry
,
7003 _("Use <tab> to autocomplete from addressbook"));
7004 hbox
= gtk_hbox_new (FALSE
, 0);
7005 gtk_widget_show(hbox
);
7006 gtk_box_pack_start (GTK_BOX (hbox
), entry
, TRUE
, TRUE
, 0);
7007 gtk_box_pack_start (GTK_BOX (hbox
), button
, FALSE
, FALSE
, 0);
7008 gtk_table_attach(GTK_TABLE(compose
->header_table
), hbox
, 1, 2,
7009 compose
->header_nextrow
, compose
->header_nextrow
+1,
7010 GTK_EXPAND
| GTK_FILL
, GTK_FILL
, 0, 0);
7012 g_signal_connect(G_OBJECT(entry
), "key-press-event",
7013 G_CALLBACK(compose_headerentry_key_press_event_cb
),
7015 g_signal_connect(G_OBJECT(entry
), "changed",
7016 G_CALLBACK(compose_headerentry_changed_cb
),
7018 g_signal_connect_after(G_OBJECT(entry
), "grab_focus",
7019 G_CALLBACK(compose_grab_focus_cb
), compose
);
7021 g_signal_connect(G_OBJECT(button
), "clicked",
7022 G_CALLBACK(compose_headerentry_button_clicked_cb
),
7026 gtk_drag_dest_set(entry
, GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
7027 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
7028 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
7029 g_signal_connect(G_OBJECT(entry
), "drag_data_received",
7030 G_CALLBACK(compose_header_drag_received_cb
),
7032 g_signal_connect(G_OBJECT(entry
), "drag-drop",
7033 G_CALLBACK(compose_drag_drop
),
7035 g_signal_connect(G_OBJECT(entry
), "populate-popup",
7036 G_CALLBACK(compose_entry_popup_extend
),
7039 address_completion_register_entry(GTK_ENTRY(entry
), TRUE
);
7041 headerentry
->compose
= compose
;
7042 headerentry
->combo
= combo
;
7043 headerentry
->entry
= entry
;
7044 headerentry
->button
= button
;
7045 headerentry
->hbox
= hbox
;
7046 headerentry
->headernum
= compose
->header_nextrow
;
7047 headerentry
->type
= PREF_NONE
;
7049 compose
->header_nextrow
++;
7050 compose
->header_last
= headerentry
;
7051 compose
->header_list
=
7052 g_slist_append(compose
->header_list
,
7056 static void compose_add_header_entry(Compose
*compose
, const gchar
*header
,
7057 gchar
*text
, ComposePrefType pref_type
)
7059 ComposeHeaderEntry
*last_header
= compose
->header_last
;
7060 gchar
*tmp
= g_strdup(text
), *email
;
7061 gboolean replyto_hdr
;
7063 replyto_hdr
= (!strcasecmp(header
,
7064 prefs_common_translated_header_name("Reply-To:")) ||
7066 prefs_common_translated_header_name("Followup-To:")) ||
7068 prefs_common_translated_header_name("In-Reply-To:")));
7070 extract_address(tmp
);
7071 email
= g_utf8_strdown(tmp
, -1);
7073 if (replyto_hdr
== FALSE
&&
7074 g_hash_table_lookup(compose
->email_hashtable
, email
) != NULL
)
7076 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
7077 header
, text
, (gint
) pref_type
);
7083 if (!strcasecmp(header
, prefs_common_translated_header_name("In-Reply-To:")))
7084 gtk_entry_set_text(GTK_ENTRY(
7085 gtk_bin_get_child(GTK_BIN(last_header
->combo
))), header
);
7087 combobox_select_by_text(GTK_COMBO_BOX(last_header
->combo
), header
);
7088 gtk_entry_set_text(GTK_ENTRY(last_header
->entry
), text
);
7089 last_header
->type
= pref_type
;
7091 if (replyto_hdr
== FALSE
)
7092 g_hash_table_insert(compose
->email_hashtable
, email
,
7093 GUINT_TO_POINTER(1));
7100 static void compose_destroy_headerentry(Compose
*compose
,
7101 ComposeHeaderEntry
*headerentry
)
7103 gchar
*text
= gtk_editable_get_chars(GTK_EDITABLE(headerentry
->entry
), 0, -1);
7106 extract_address(text
);
7107 email
= g_utf8_strdown(text
, -1);
7108 g_hash_table_remove(compose
->email_hashtable
, email
);
7112 gtk_widget_destroy(headerentry
->combo
);
7113 gtk_widget_destroy(headerentry
->entry
);
7114 gtk_widget_destroy(headerentry
->button
);
7115 gtk_widget_destroy(headerentry
->hbox
);
7116 g_free(headerentry
);
7119 static void compose_remove_header_entries(Compose
*compose
)
7122 for (list
= compose
->header_list
; list
; list
= list
->next
)
7123 compose_destroy_headerentry(compose
, (ComposeHeaderEntry
*)list
->data
);
7125 compose
->header_last
= NULL
;
7126 g_slist_free(compose
->header_list
);
7127 compose
->header_list
= NULL
;
7128 compose
->header_nextrow
= 1;
7129 compose_create_header_entry(compose
);
7132 static GtkWidget
*compose_create_header(Compose
*compose
)
7134 GtkWidget
*from_optmenu_hbox
;
7135 GtkWidget
*header_table_main
;
7136 GtkWidget
*header_scrolledwin
;
7137 GtkWidget
*header_table
;
7139 /* parent with account selection and from header */
7140 header_table_main
= gtk_table_new(2, 2, FALSE
);
7141 gtk_widget_show(header_table_main
);
7142 gtk_container_set_border_width(GTK_CONTAINER(header_table_main
), BORDER_WIDTH
);
7144 from_optmenu_hbox
= compose_account_option_menu_create(compose
);
7145 gtk_table_attach(GTK_TABLE(header_table_main
), from_optmenu_hbox
,
7146 0, 2, 0, 1, GTK_EXPAND
| GTK_FILL
, GTK_SHRINK
, 0, 0);
7148 /* child with header labels and entries */
7149 header_scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
7150 gtk_widget_show(header_scrolledwin
);
7151 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin
), GTK_POLICY_NEVER
, GTK_POLICY_AUTOMATIC
);
7153 header_table
= gtk_table_new(2, 2, FALSE
);
7154 gtk_widget_show(header_table
);
7155 gtk_container_set_border_width(GTK_CONTAINER(header_table
), 0);
7156 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin
), header_table
);
7157 gtk_container_set_focus_vadjustment(GTK_CONTAINER(header_table
),
7158 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(header_scrolledwin
)));
7159 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN(header_scrolledwin
))), GTK_SHADOW_NONE
);
7161 gtk_table_attach(GTK_TABLE(header_table_main
), header_scrolledwin
,
7162 0, 2, 1, 2, GTK_EXPAND
| GTK_FILL
, GTK_EXPAND
| GTK_FILL
, 0, 2);
7164 compose
->header_table
= header_table
;
7165 compose
->header_list
= NULL
;
7166 compose
->header_nextrow
= 0;
7168 compose_create_header_entry(compose
);
7170 compose
->table
= NULL
;
7172 return header_table_main
;
7175 static gboolean
popup_attach_button_pressed(GtkWidget
*widget
, gpointer data
)
7177 Compose
*compose
= (Compose
*)data
;
7178 GdkEventButton event
;
7181 event
.time
= gtk_get_current_event_time();
7183 return attach_button_pressed(compose
->attach_clist
, &event
, compose
);
7186 static GtkWidget
*compose_create_attach(Compose
*compose
)
7188 GtkWidget
*attach_scrwin
;
7189 GtkWidget
*attach_clist
;
7191 GtkListStore
*store
;
7192 GtkCellRenderer
*renderer
;
7193 GtkTreeViewColumn
*column
;
7194 GtkTreeSelection
*selection
;
7196 /* attachment list */
7197 attach_scrwin
= gtk_scrolled_window_new(NULL
, NULL
);
7198 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin
),
7199 GTK_POLICY_AUTOMATIC
,
7200 GTK_POLICY_AUTOMATIC
);
7201 gtk_widget_set_size_request(attach_scrwin
, -1, 80);
7203 store
= gtk_list_store_new(N_ATTACH_COLS
,
7209 G_TYPE_AUTO_POINTER
,
7211 attach_clist
= GTK_WIDGET(gtk_tree_view_new_with_model
7212 (GTK_TREE_MODEL(store
)));
7213 gtk_container_add(GTK_CONTAINER(attach_scrwin
), attach_clist
);
7214 g_object_unref(store
);
7216 renderer
= gtk_cell_renderer_text_new();
7217 column
= gtk_tree_view_column_new_with_attributes
7218 (_("Mime type"), renderer
, "text",
7219 COL_MIMETYPE
, NULL
);
7220 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7222 renderer
= gtk_cell_renderer_text_new();
7223 column
= gtk_tree_view_column_new_with_attributes
7224 (_("Size"), renderer
, "text",
7226 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7228 renderer
= gtk_cell_renderer_text_new();
7229 column
= gtk_tree_view_column_new_with_attributes
7230 (_("Name"), renderer
, "text",
7232 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist
), column
);
7234 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist
),
7235 prefs_common
.use_stripes_everywhere
);
7236 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist
));
7237 gtk_tree_selection_set_mode(selection
, GTK_SELECTION_MULTIPLE
);
7239 g_signal_connect(G_OBJECT(attach_clist
), "row_activated",
7240 G_CALLBACK(attach_selected
), compose
);
7241 g_signal_connect(G_OBJECT(attach_clist
), "button_press_event",
7242 G_CALLBACK(attach_button_pressed
), compose
);
7243 g_signal_connect(G_OBJECT(attach_clist
), "popup-menu",
7244 G_CALLBACK(popup_attach_button_pressed
), compose
);
7245 g_signal_connect(G_OBJECT(attach_clist
), "key_press_event",
7246 G_CALLBACK(attach_key_pressed
), compose
);
7249 gtk_drag_dest_set(attach_clist
,
7250 GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
7251 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
7252 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
7253 g_signal_connect(G_OBJECT(attach_clist
), "drag_data_received",
7254 G_CALLBACK(compose_attach_drag_received_cb
),
7256 g_signal_connect(G_OBJECT(attach_clist
), "drag-drop",
7257 G_CALLBACK(compose_drag_drop
),
7260 compose
->attach_scrwin
= attach_scrwin
;
7261 compose
->attach_clist
= attach_clist
;
7263 return attach_scrwin
;
7266 static void compose_savemsg_checkbtn_cb(GtkWidget
*widget
, Compose
*compose
);
7267 static void compose_savemsg_select_cb(GtkWidget
*widget
, Compose
*compose
);
7269 static GtkWidget
*compose_create_others(Compose
*compose
)
7272 GtkWidget
*savemsg_checkbtn
;
7273 GtkWidget
*savemsg_combo
;
7274 GtkWidget
*savemsg_select
;
7277 gchar
*folderidentifier
;
7279 /* Table for settings */
7280 table
= gtk_table_new(3, 1, FALSE
);
7281 gtk_container_set_border_width(GTK_CONTAINER(table
), BORDER_WIDTH
);
7282 gtk_widget_show(table
);
7283 gtk_table_set_row_spacings(GTK_TABLE(table
), VSPACING_NARROW
);
7286 /* Save Message to folder */
7287 savemsg_checkbtn
= gtk_check_button_new_with_label(_("Save Message to "));
7288 gtk_widget_show(savemsg_checkbtn
);
7289 gtk_table_attach(GTK_TABLE(table
), savemsg_checkbtn
, 0, 1, rowcount
, rowcount
+ 1, GTK_SHRINK
| GTK_FILL
, GTK_SHRINK
, 0, 0);
7290 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
7291 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn
), prefs_common
.savemsg
);
7293 g_signal_connect(G_OBJECT(savemsg_checkbtn
), "toggled",
7294 G_CALLBACK(compose_savemsg_checkbtn_cb
), compose
);
7296 #if !GTK_CHECK_VERSION(2, 24, 0)
7297 savemsg_combo
= gtk_combo_box_entry_new_text();
7299 savemsg_combo
= gtk_combo_box_text_new_with_entry();
7301 compose
->savemsg_checkbtn
= savemsg_checkbtn
;
7302 compose
->savemsg_combo
= savemsg_combo
;
7303 gtk_widget_show(savemsg_combo
);
7305 if (prefs_common
.compose_save_to_history
)
7306 #if !GTK_CHECK_VERSION(2, 24, 0)
7307 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo
),
7308 prefs_common
.compose_save_to_history
);
7310 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(savemsg_combo
),
7311 prefs_common
.compose_save_to_history
);
7313 gtk_table_attach(GTK_TABLE(table
), savemsg_combo
, 1, 2, rowcount
, rowcount
+ 1, GTK_FILL
|GTK_EXPAND
, GTK_SHRINK
, 0, 0);
7314 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo
), prefs_common
.savemsg
);
7315 g_signal_connect_after(G_OBJECT(savemsg_combo
), "grab_focus",
7316 G_CALLBACK(compose_grab_focus_cb
), compose
);
7317 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
7318 folderidentifier
= folder_item_get_identifier(account_get_special_folder
7319 (compose
->account
, F_OUTBOX
));
7320 compose_set_save_to(compose
, folderidentifier
);
7321 g_free(folderidentifier
);
7324 savemsg_select
= gtkut_get_browse_file_btn(_("_Browse"));
7325 gtk_widget_show(savemsg_select
);
7326 gtk_table_attach(GTK_TABLE(table
), savemsg_select
, 2, 3, rowcount
, rowcount
+ 1, GTK_SHRINK
| GTK_FILL
, GTK_SHRINK
, 0, 0);
7327 g_signal_connect(G_OBJECT(savemsg_select
), "clicked",
7328 G_CALLBACK(compose_savemsg_select_cb
),
7334 static void compose_savemsg_checkbtn_cb(GtkWidget
*widget
, Compose
*compose
)
7336 gtk_widget_set_sensitive(GTK_WIDGET(compose
->savemsg_combo
),
7337 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
)));
7340 static void compose_savemsg_select_cb(GtkWidget
*widget
, Compose
*compose
)
7345 dest
= foldersel_folder_sel(NULL
, FOLDER_SEL_COPY
, NULL
, FALSE
);
7348 path
= folder_item_get_identifier(dest
);
7350 compose_set_save_to(compose
, path
);
7354 static void entry_paste_clipboard(Compose
*compose
, GtkWidget
*entry
, gboolean wrap
,
7355 GdkAtom clip
, GtkTextIter
*insert_place
);
7358 static gboolean
text_clicked(GtkWidget
*text
, GdkEventButton
*event
,
7362 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
7364 if (event
->button
== 3) {
7366 GtkTextIter sel_start
, sel_end
;
7367 gboolean stuff_selected
;
7369 /* move the cursor to allow GtkAspell to check the word
7370 * under the mouse */
7371 if (event
->x
&& event
->y
) {
7372 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text
),
7373 GTK_TEXT_WINDOW_TEXT
, event
->x
, event
->y
,
7375 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
7378 GtkTextMark
*mark
= gtk_text_buffer_get_insert(buffer
);
7379 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
7382 stuff_selected
= gtk_text_buffer_get_selection_bounds(
7384 &sel_start
, &sel_end
);
7386 gtk_text_buffer_place_cursor (buffer
, &iter
);
7387 /* reselect stuff */
7389 && gtk_text_iter_in_range(&iter
, &sel_start
, &sel_end
)) {
7390 gtk_text_buffer_select_range(buffer
,
7391 &sel_start
, &sel_end
);
7393 return FALSE
; /* pass the event so that the right-click goes through */
7396 if (event
->button
== 2) {
7401 /* get the middle-click position to paste at the correct place */
7402 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text
),
7403 GTK_TEXT_WINDOW_TEXT
, event
->x
, event
->y
,
7405 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text
),
7408 entry_paste_clipboard(compose
, text
,
7409 prefs_common
.linewrap_pastes
,
7410 GDK_SELECTION_PRIMARY
, &iter
);
7418 static void compose_spell_menu_changed(void *data
)
7420 Compose
*compose
= (Compose
*)data
;
7422 GtkWidget
*menuitem
;
7423 GtkWidget
*parent_item
;
7424 GtkMenu
*menu
= GTK_MENU(gtk_menu_new());
7427 if (compose
->gtkaspell
== NULL
)
7430 parent_item
= gtk_ui_manager_get_widget(compose
->ui_manager
,
7431 "/Menu/Spelling/Options");
7433 /* setting the submenu removes /Spelling/Options from the factory
7434 * so we need to save it */
7436 if (parent_item
== NULL
) {
7437 parent_item
= compose
->aspell_options_menu
;
7438 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item
), NULL
);
7440 compose
->aspell_options_menu
= parent_item
;
7442 spell_menu
= gtkaspell_make_config_menu(compose
->gtkaspell
);
7444 spell_menu
= g_slist_reverse(spell_menu
);
7445 for (items
= spell_menu
;
7446 items
; items
= items
->next
) {
7447 menuitem
= GTK_WIDGET(GTK_MENU_ITEM(items
->data
));
7448 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), GTK_WIDGET(menuitem
));
7449 gtk_widget_show(GTK_WIDGET(menuitem
));
7451 g_slist_free(spell_menu
);
7453 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item
), GTK_WIDGET(menu
));
7454 gtk_widget_show(parent_item
);
7457 static void compose_dict_changed(void *data
)
7459 Compose
*compose
= (Compose
*) data
;
7461 if(!compose
->gtkaspell
)
7463 if(compose
->gtkaspell
->recheck_when_changing_dict
== FALSE
)
7466 gtkaspell_highlight_all(compose
->gtkaspell
);
7467 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose
->subject_entry
));
7471 static gboolean
compose_popup_menu(GtkWidget
*widget
, gpointer data
)
7473 Compose
*compose
= (Compose
*)data
;
7474 GdkEventButton event
;
7477 event
.time
= gtk_get_current_event_time();
7481 return text_clicked(compose
->text
, &event
, compose
);
7484 static gboolean compose_force_window_origin
= TRUE
;
7485 static Compose
*compose_create(PrefsAccount
*account
,
7494 GtkWidget
*handlebox
;
7496 GtkWidget
*notebook
;
7498 GtkWidget
*attach_hbox
;
7499 GtkWidget
*attach_lab1
;
7500 GtkWidget
*attach_lab2
;
7505 GtkWidget
*subject_hbox
;
7506 GtkWidget
*subject_frame
;
7507 GtkWidget
*subject_entry
;
7511 GtkWidget
*edit_vbox
;
7512 GtkWidget
*ruler_hbox
;
7514 GtkWidget
*scrolledwin
;
7516 GtkTextBuffer
*buffer
;
7517 GtkClipboard
*clipboard
;
7519 UndoMain
*undostruct
;
7521 GtkWidget
*popupmenu
;
7522 GtkWidget
*tmpl_menu
;
7523 GtkActionGroup
*action_group
= NULL
;
7526 GtkAspell
* gtkaspell
= NULL
;
7529 static GdkGeometry geometry
;
7531 cm_return_val_if_fail(account
!= NULL
, NULL
);
7533 debug_print("Creating compose window...\n");
7534 compose
= g_new0(Compose
, 1);
7536 compose
->batch
= batch
;
7537 compose
->account
= account
;
7538 compose
->folder
= folder
;
7540 compose
->mutex
= cm_mutex_new();
7541 compose
->set_cursor_pos
= -1;
7543 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "compose");
7545 gtk_window_set_resizable(GTK_WINDOW(window
), TRUE
);
7546 gtk_widget_set_size_request(window
, prefs_common
.compose_width
,
7547 prefs_common
.compose_height
);
7549 if (!geometry
.max_width
) {
7550 geometry
.max_width
= gdk_screen_width();
7551 geometry
.max_height
= gdk_screen_height();
7554 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
,
7555 &geometry
, GDK_HINT_MAX_SIZE
);
7556 if (!geometry
.min_width
) {
7557 geometry
.min_width
= 600;
7558 geometry
.min_height
= 440;
7560 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
,
7561 &geometry
, GDK_HINT_MIN_SIZE
);
7563 #ifndef GENERIC_UMPC
7564 if (compose_force_window_origin
)
7565 gtk_window_move(GTK_WINDOW(window
), prefs_common
.compose_x
,
7566 prefs_common
.compose_y
);
7568 g_signal_connect(G_OBJECT(window
), "delete_event",
7569 G_CALLBACK(compose_delete_cb
), compose
);
7570 MANAGE_WINDOW_SIGNALS_CONNECT(window
);
7571 gtk_widget_realize(window
);
7573 gtkut_widget_set_composer_icon(window
);
7575 vbox
= gtk_vbox_new(FALSE
, 0);
7576 gtk_container_add(GTK_CONTAINER(window
), vbox
);
7578 compose
->ui_manager
= gtk_ui_manager_new();
7579 action_group
= cm_menu_create_action_group_full(compose
->ui_manager
,"Menu", compose_entries
,
7580 G_N_ELEMENTS(compose_entries
), (gpointer
)compose
);
7581 gtk_action_group_add_toggle_actions(action_group
, compose_toggle_entries
,
7582 G_N_ELEMENTS(compose_toggle_entries
), (gpointer
)compose
);
7583 gtk_action_group_add_radio_actions(action_group
, compose_radio_rm_entries
,
7584 G_N_ELEMENTS(compose_radio_rm_entries
), COMPOSE_REPLY
, G_CALLBACK(compose_reply_change_mode_cb
), (gpointer
)compose
);
7585 gtk_action_group_add_radio_actions(action_group
, compose_radio_prio_entries
,
7586 G_N_ELEMENTS(compose_radio_prio_entries
), PRIORITY_NORMAL
, G_CALLBACK(compose_set_priority_cb
), (gpointer
)compose
);
7587 gtk_action_group_add_radio_actions(action_group
, compose_radio_enc_entries
,
7588 G_N_ELEMENTS(compose_radio_enc_entries
), C_AUTO
, G_CALLBACK(compose_set_encoding_cb
), (gpointer
)compose
);
7590 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/", "Menu", NULL
, GTK_UI_MANAGER_MENUBAR
)
7592 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU
)
7593 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU
)
7595 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU
)
7597 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU
)
7598 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU
)
7599 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU
)
7602 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM
)
7603 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM
)
7604 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7605 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM
)
7606 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM
)
7607 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM
)
7608 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "ReplaceSig", "Message/ReplaceSig", GTK_UI_MANAGER_MENUITEM
)
7609 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7610 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM
)
7611 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7612 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM
)
7613 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR
)
7614 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM
)
7617 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM
)
7618 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM
)
7619 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7621 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM
)
7622 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM
)
7623 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM
)
7625 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU
)
7626 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM
)
7627 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM
)
7628 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM
)
7630 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM
)
7632 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU
)
7633 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM
)
7634 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM
)
7635 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM
)
7636 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM
)
7637 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM
)
7638 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM
)
7639 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM
)
7640 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM
)
7641 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM
)
7642 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM
)
7643 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM
)
7644 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM
)
7645 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM
)
7646 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM
)
7648 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7650 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM
)
7651 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM
)
7652 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM
)
7653 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM
)
7654 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM
)
7656 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR
)
7658 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM
)
7662 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM
)
7663 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM
)
7664 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM
)
7665 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM
)
7666 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR
)
7667 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU
)
7671 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU
)
7672 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM
)
7673 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM
)
7674 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM
)
7675 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM
)
7677 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7678 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU
)
7679 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
7680 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM
)
7681 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM
)
7684 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7685 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU
)
7686 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM
)
7687 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM
)
7688 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM
)
7689 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM
)
7690 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM
)
7692 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7693 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM
)
7694 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7695 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM
)
7696 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR
)
7698 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU
)
7700 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_AUTO
, "Options/Encoding/"CS_AUTO
, GTK_UI_MANAGER_MENUITEM
)
7701 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR
)
7702 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_US_ASCII
, "Options/Encoding/"CS_US_ASCII
, GTK_UI_MANAGER_MENUITEM
)
7703 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_UTF_8
, "Options/Encoding/"CS_UTF_8
, GTK_UI_MANAGER_MENUITEM
)
7704 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR
)
7706 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU
)
7707 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
)
7708 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
)
7709 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252
, "Options/Encoding/Western/"CS_WINDOWS_1252
, GTK_UI_MANAGER_MENUITEM
)
7711 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_2
, "Options/Encoding/"CS_ISO_8859_2
, GTK_UI_MANAGER_MENUITEM
)
7713 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU
)
7714 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
)
7715 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
)
7717 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_7
, "Options/Encoding/"CS_ISO_8859_7
, GTK_UI_MANAGER_MENUITEM
)
7719 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU
)
7720 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
)
7721 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255
, "Options/Encoding/Hebrew/"CS_WINDOWS_1255
, GTK_UI_MANAGER_MENUITEM
)
7723 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU
)
7724 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
)
7725 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256
, "Options/Encoding/Arabic/"CS_WINDOWS_1256
, GTK_UI_MANAGER_MENUITEM
)
7727 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", CS_ISO_8859_9
, "Options/Encoding/"CS_ISO_8859_9
, GTK_UI_MANAGER_MENUITEM
)
7729 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU
)
7730 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
)
7731 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R
, "Options/Encoding/Cyrillic/"CS_KOI8_R
, GTK_UI_MANAGER_MENUITEM
)
7732 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_MACCYR
, "Options/Encoding/Cyrillic/"CS_MACCYR
, GTK_UI_MANAGER_MENUITEM
)
7733 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U
, "Options/Encoding/Cyrillic/"CS_KOI8_U
, GTK_UI_MANAGER_MENUITEM
)
7734 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251
, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251
, GTK_UI_MANAGER_MENUITEM
)
7736 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU
)
7737 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
)
7738 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
)
7739 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_EUC_JP
, "Options/Encoding/Japanese/"CS_EUC_JP
, GTK_UI_MANAGER_MENUITEM
)
7740 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS
, "Options/Encoding/Japanese/"CS_SHIFT_JIS
, GTK_UI_MANAGER_MENUITEM
)
7742 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU
)
7743 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GB18030
, "Options/Encoding/Chinese/"CS_GB18030
, GTK_UI_MANAGER_MENUITEM
)
7744 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GB2312
, "Options/Encoding/Chinese/"CS_GB2312
, GTK_UI_MANAGER_MENUITEM
)
7745 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_GBK
, "Options/Encoding/Chinese/"CS_GBK
, GTK_UI_MANAGER_MENUITEM
)
7746 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_BIG5
, "Options/Encoding/Chinese/"CS_BIG5
, GTK_UI_MANAGER_MENUITEM
)
7747 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Chinese", CS_EUC_TW
, "Options/Encoding/Chinese/"CS_EUC_TW
, GTK_UI_MANAGER_MENUITEM
)
7749 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU
)
7750 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Korean", CS_EUC_KR
, "Options/Encoding/Korean/"CS_EUC_KR
, GTK_UI_MANAGER_MENUITEM
)
7751 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
)
7753 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU
)
7754 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Thai", CS_TIS_620
, "Options/Encoding/Thai/"CS_TIS_620
, GTK_UI_MANAGER_MENUITEM
)
7755 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874
, "Options/Encoding/Thai/"CS_WINDOWS_874
, GTK_UI_MANAGER_MENUITEM
)
7759 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM
)
7760 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM
)
7761 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU
)
7762 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
7763 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU
)
7764 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM
)
7767 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM
)
7769 menubar
= gtk_ui_manager_get_widget(compose
->ui_manager
, "/Menu");
7770 gtk_widget_show_all(menubar
);
7772 gtk_window_add_accel_group(GTK_WINDOW(window
), gtk_ui_manager_get_accel_group(compose
->ui_manager
));
7773 gtk_box_pack_start(GTK_BOX(vbox
), menubar
, FALSE
, TRUE
, 0);
7775 if (prefs_common
.toolbar_detachable
) {
7776 handlebox
= gtk_handle_box_new();
7778 handlebox
= gtk_hbox_new(FALSE
, 0);
7780 gtk_box_pack_start(GTK_BOX(vbox
), handlebox
, FALSE
, FALSE
, 0);
7782 gtk_widget_realize(handlebox
);
7783 compose
->toolbar
= toolbar_create(TOOLBAR_COMPOSE
, handlebox
,
7786 vbox2
= gtk_vbox_new(FALSE
, 2);
7787 gtk_box_pack_start(GTK_BOX(vbox
), vbox2
, TRUE
, TRUE
, 0);
7788 gtk_container_set_border_width(GTK_CONTAINER(vbox2
), 0);
7791 notebook
= gtk_notebook_new();
7792 gtk_widget_show(notebook
);
7794 /* header labels and entries */
7795 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
7796 compose_create_header(compose
),
7797 gtk_label_new_with_mnemonic(_("Hea_der")));
7798 /* attachment list */
7799 attach_hbox
= gtk_hbox_new(FALSE
, 0);
7800 gtk_widget_show(attach_hbox
);
7802 attach_lab1
= gtk_label_new_with_mnemonic(_("_Attachments"));
7803 gtk_widget_show(attach_lab1
);
7804 gtk_box_pack_start(GTK_BOX(attach_hbox
), attach_lab1
, TRUE
, TRUE
, 0);
7806 attach_lab2
= gtk_label_new("");
7807 gtk_widget_show(attach_lab2
);
7808 gtk_box_pack_start(GTK_BOX(attach_hbox
), attach_lab2
, FALSE
, FALSE
, 0);
7810 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
7811 compose_create_attach(compose
),
7814 gtk_notebook_append_page(GTK_NOTEBOOK(notebook
),
7815 compose_create_others(compose
),
7816 gtk_label_new_with_mnemonic(_("Othe_rs")));
7819 subject_hbox
= gtk_hbox_new(FALSE
, 0);
7820 gtk_widget_show(subject_hbox
);
7822 subject_frame
= gtk_frame_new(NULL
);
7823 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame
), GTK_SHADOW_NONE
);
7824 gtk_box_pack_start(GTK_BOX(subject_hbox
), subject_frame
, TRUE
, TRUE
, 0);
7825 gtk_widget_show(subject_frame
);
7827 subject
= gtk_hbox_new(FALSE
, HSPACING_NARROW
);
7828 gtk_container_set_border_width(GTK_CONTAINER(subject
), 0);
7829 gtk_widget_show(subject
);
7831 label
= gtk_label_new_with_mnemonic(_("S_ubject:"));
7832 gtk_box_pack_start(GTK_BOX(subject
), label
, FALSE
, FALSE
, 0);
7833 gtk_widget_show(label
);
7836 subject_entry
= claws_spell_entry_new();
7838 subject_entry
= gtk_entry_new();
7840 gtk_box_pack_start(GTK_BOX(subject
), subject_entry
, TRUE
, TRUE
, 0);
7841 g_signal_connect_after(G_OBJECT(subject_entry
), "grab_focus",
7842 G_CALLBACK(compose_grab_focus_cb
), compose
);
7843 gtk_label_set_mnemonic_widget(GTK_LABEL(label
), subject_entry
);
7844 gtk_widget_show(subject_entry
);
7845 compose
->subject_entry
= subject_entry
;
7846 gtk_container_add(GTK_CONTAINER(subject_frame
), subject
);
7848 edit_vbox
= gtk_vbox_new(FALSE
, 0);
7850 gtk_box_pack_start(GTK_BOX(edit_vbox
), subject_hbox
, FALSE
, FALSE
, 0);
7853 ruler_hbox
= gtk_hbox_new(FALSE
, 0);
7854 gtk_box_pack_start(GTK_BOX(edit_vbox
), ruler_hbox
, FALSE
, FALSE
, 0);
7856 ruler
= gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL
);
7857 gtk_shruler_set_range(GTK_SHRULER(ruler
), 0.0, 100.0, 1.0);
7858 gtk_box_pack_start(GTK_BOX(ruler_hbox
), ruler
, TRUE
, TRUE
,
7862 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
7863 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
7864 GTK_POLICY_AUTOMATIC
,
7865 GTK_POLICY_AUTOMATIC
);
7866 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin
),
7868 gtk_box_pack_start(GTK_BOX(edit_vbox
), scrolledwin
, TRUE
, TRUE
, 0);
7870 text
= gtk_text_view_new();
7871 if (prefs_common
.show_compose_margin
) {
7872 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text
), 6);
7873 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text
), 6);
7875 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
7876 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text
), GTK_WRAP_WORD_CHAR
);
7877 gtk_text_view_set_editable(GTK_TEXT_VIEW(text
), TRUE
);
7878 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
7879 gtk_text_buffer_add_selection_clipboard(buffer
, clipboard
);
7881 gtk_container_add(GTK_CONTAINER(scrolledwin
), text
);
7882 g_signal_connect_after(G_OBJECT(text
), "size_allocate",
7883 G_CALLBACK(compose_edit_size_alloc
),
7885 g_signal_connect(G_OBJECT(buffer
), "changed",
7886 G_CALLBACK(compose_changed_cb
), compose
);
7887 g_signal_connect(G_OBJECT(text
), "grab_focus",
7888 G_CALLBACK(compose_grab_focus_cb
), compose
);
7889 g_signal_connect(G_OBJECT(buffer
), "insert_text",
7890 G_CALLBACK(text_inserted
), compose
);
7891 g_signal_connect(G_OBJECT(text
), "button_press_event",
7892 G_CALLBACK(text_clicked
), compose
);
7893 g_signal_connect(G_OBJECT(text
), "popup-menu",
7894 G_CALLBACK(compose_popup_menu
), compose
);
7895 g_signal_connect(G_OBJECT(subject_entry
), "changed",
7896 G_CALLBACK(compose_changed_cb
), compose
);
7897 g_signal_connect(G_OBJECT(subject_entry
), "activate",
7898 G_CALLBACK(compose_subject_entry_activated
), compose
);
7901 gtk_drag_dest_set(text
, GTK_DEST_DEFAULT_ALL
, compose_mime_types
,
7902 sizeof(compose_mime_types
)/sizeof(compose_mime_types
[0]),
7903 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
7904 g_signal_connect(G_OBJECT(text
), "drag_data_received",
7905 G_CALLBACK(compose_insert_drag_received_cb
),
7907 g_signal_connect(G_OBJECT(text
), "drag-drop",
7908 G_CALLBACK(compose_drag_drop
),
7910 g_signal_connect(G_OBJECT(text
), "key-press-event",
7911 G_CALLBACK(completion_set_focus_to_subject
),
7913 gtk_widget_show_all(vbox
);
7915 /* pane between attach clist and text */
7916 paned
= gtk_vpaned_new();
7917 gtk_container_add(GTK_CONTAINER(vbox2
), paned
);
7918 gtk_paned_pack1(GTK_PANED(paned
), notebook
, FALSE
, FALSE
);
7919 gtk_paned_pack2(GTK_PANED(paned
), edit_vbox
, TRUE
, FALSE
);
7920 gtk_paned_set_position(GTK_PANED(paned
), prefs_common
.compose_notebook_height
);
7921 g_signal_connect(G_OBJECT(notebook
), "size_allocate",
7922 G_CALLBACK(compose_notebook_size_alloc
), paned
);
7924 gtk_widget_show_all(paned
);
7927 if (prefs_common
.textfont
) {
7928 PangoFontDescription
*font_desc
;
7930 font_desc
= pango_font_description_from_string
7931 (prefs_common
.textfont
);
7933 gtk_widget_modify_font(text
, font_desc
);
7934 pango_font_description_free(font_desc
);
7938 gtk_action_group_add_actions(action_group
, compose_popup_entries
,
7939 G_N_ELEMENTS(compose_popup_entries
), (gpointer
)compose
);
7940 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/", "Popup", NULL
, GTK_UI_MANAGER_MENUBAR
)
7941 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU
)
7942 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM
)
7943 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM
)
7944 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR
)
7945 MENUITEM_ADDUI_MANAGER(compose
->ui_manager
, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM
)
7947 popupmenu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose
->ui_manager
, "/Popup/Compose")));
7949 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", FALSE
);
7950 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", FALSE
);
7951 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/RemoveReferences", FALSE
);
7953 tmpl_menu
= gtk_ui_manager_get_widget(compose
->ui_manager
, "/Menu/Tools/Template");
7955 undostruct
= undo_init(text
);
7956 undo_set_change_state_func(undostruct
, &compose_undo_state_changed
,
7959 address_completion_start(window
);
7961 compose
->window
= window
;
7962 compose
->vbox
= vbox
;
7963 compose
->menubar
= menubar
;
7964 compose
->handlebox
= handlebox
;
7966 compose
->vbox2
= vbox2
;
7968 compose
->paned
= paned
;
7970 compose
->attach_label
= attach_lab2
;
7972 compose
->notebook
= notebook
;
7973 compose
->edit_vbox
= edit_vbox
;
7974 compose
->ruler_hbox
= ruler_hbox
;
7975 compose
->ruler
= ruler
;
7976 compose
->scrolledwin
= scrolledwin
;
7977 compose
->text
= text
;
7979 compose
->focused_editable
= NULL
;
7981 compose
->popupmenu
= popupmenu
;
7983 compose
->tmpl_menu
= tmpl_menu
;
7985 compose
->mode
= mode
;
7986 compose
->rmode
= mode
;
7988 compose
->targetinfo
= NULL
;
7989 compose
->replyinfo
= NULL
;
7990 compose
->fwdinfo
= NULL
;
7992 compose
->email_hashtable
= g_hash_table_new_full(g_str_hash
,
7993 g_str_equal
, (GDestroyNotify
) g_free
, NULL
);
7995 compose
->replyto
= NULL
;
7997 compose
->bcc
= NULL
;
7998 compose
->followup_to
= NULL
;
8000 compose
->ml_post
= NULL
;
8002 compose
->inreplyto
= NULL
;
8003 compose
->references
= NULL
;
8004 compose
->msgid
= NULL
;
8005 compose
->boundary
= NULL
;
8007 compose
->autowrap
= prefs_common
.autowrap
;
8008 compose
->autoindent
= prefs_common
.auto_indent
;
8009 compose
->use_signing
= FALSE
;
8010 compose
->use_encryption
= FALSE
;
8011 compose
->privacy_system
= NULL
;
8012 compose
->encdata
= NULL
;
8014 compose
->modified
= FALSE
;
8016 compose
->return_receipt
= FALSE
;
8018 compose
->to_list
= NULL
;
8019 compose
->newsgroup_list
= NULL
;
8021 compose
->undostruct
= undostruct
;
8023 compose
->sig_str
= NULL
;
8025 compose
->exteditor_file
= NULL
;
8026 compose
->exteditor_pid
= -1;
8027 compose
->exteditor_tag
= -1;
8028 compose
->exteditor_socket
= NULL
;
8029 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
; /* inhibit auto-drafting while loading */
8031 compose
->folder_update_callback_id
=
8032 hooks_register_hook(FOLDER_UPDATE_HOOKLIST
,
8033 compose_update_folder_hook
,
8034 (gpointer
) compose
);
8037 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
8038 if (mode
!= COMPOSE_REDIRECT
) {
8039 if (prefs_common
.enable_aspell
&& prefs_common
.dictionary
&&
8040 strcmp(prefs_common
.dictionary
, "")) {
8041 gtkaspell
= gtkaspell_new(prefs_common
.dictionary
,
8042 prefs_common
.alt_dictionary
,
8043 conv_get_locale_charset_str(),
8044 prefs_common
.misspelled_col
,
8045 prefs_common
.check_while_typing
,
8046 prefs_common
.recheck_when_changing_dict
,
8047 prefs_common
.use_alternate
,
8048 prefs_common
.use_both_dicts
,
8049 GTK_TEXT_VIEW(text
),
8050 GTK_WINDOW(compose
->window
),
8051 compose_dict_changed
,
8052 compose_spell_menu_changed
,
8055 alertpanel_error(_("Spell checker could not "
8057 gtkaspell_checkers_strerror());
8058 gtkaspell_checkers_reset_error();
8060 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", TRUE
);
8064 compose
->gtkaspell
= gtkaspell
;
8065 compose_spell_menu_changed(compose
);
8066 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry
), gtkaspell
);
8069 compose_select_account(compose
, account
, TRUE
);
8071 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoWrap", prefs_common
.autowrap
);
8072 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Edit/AutoIndent", prefs_common
.auto_indent
);
8074 if (account
->set_autocc
&& account
->auto_cc
&& mode
!= COMPOSE_REEDIT
)
8075 compose_entry_append(compose
, account
->auto_cc
, COMPOSE_CC
, PREF_ACCOUNT
);
8077 if (account
->set_autobcc
&& account
->auto_bcc
&& mode
!= COMPOSE_REEDIT
)
8078 compose_entry_append(compose
, account
->auto_bcc
, COMPOSE_BCC
, PREF_ACCOUNT
);
8080 if (account
->set_autoreplyto
&& account
->auto_replyto
&& mode
!= COMPOSE_REEDIT
)
8081 compose_entry_append(compose
, account
->auto_replyto
, COMPOSE_REPLYTO
, PREF_ACCOUNT
);
8083 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/ReplyMode", compose
->mode
== COMPOSE_REPLY
);
8084 if (account
->protocol
!= A_NNTP
)
8085 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))),
8086 prefs_common_translated_header_name("To:"));
8088 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose
->header_last
->combo
)))),
8089 prefs_common_translated_header_name("Newsgroups:"));
8091 #ifndef USE_ALT_ADDRBOOK
8092 addressbook_set_target_compose(compose
);
8094 if (mode
!= COMPOSE_REDIRECT
)
8095 compose_set_template_menu(compose
);
8097 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools/Template", FALSE
);
8100 compose_list
= g_list_append(compose_list
, compose
);
8102 if (!prefs_common
.show_ruler
)
8103 gtk_widget_hide(ruler_hbox
);
8105 cm_toggle_menu_set_active_full(compose
->ui_manager
, "Menu/Tools/ShowRuler", prefs_common
.show_ruler
);
8108 compose
->priority
= PRIORITY_NORMAL
;
8109 compose_update_priority_menu_item(compose
);
8111 compose_set_out_encoding(compose
);
8114 compose_update_actions_menu(compose
);
8116 /* Privacy Systems menu */
8117 compose_update_privacy_systems_menu(compose
);
8119 activate_privacy_system(compose
, account
, TRUE
);
8120 toolbar_set_style(compose
->toolbar
->toolbar
, compose
->handlebox
, prefs_common
.toolbar_style
);
8122 gtk_widget_realize(window
);
8124 gtk_widget_show(window
);
8130 static GtkWidget
*compose_account_option_menu_create(Compose
*compose
)
8135 GtkWidget
*optmenubox
;
8136 GtkWidget
*fromlabel
;
8139 GtkWidget
*from_name
= NULL
;
8141 gint num
= 0, def_menu
= 0;
8143 accounts
= account_get_list();
8144 cm_return_val_if_fail(accounts
!= NULL
, NULL
);
8146 optmenubox
= gtk_event_box_new();
8147 optmenu
= gtkut_sc_combobox_create(optmenubox
, FALSE
);
8148 menu
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
)));
8150 hbox
= gtk_hbox_new(FALSE
, 4);
8151 from_name
= gtk_entry_new();
8153 g_signal_connect_after(G_OBJECT(from_name
), "grab_focus",
8154 G_CALLBACK(compose_grab_focus_cb
), compose
);
8155 g_signal_connect_after(G_OBJECT(from_name
), "activate",
8156 G_CALLBACK(from_name_activate_cb
), optmenu
);
8158 for (; accounts
!= NULL
; accounts
= accounts
->next
, num
++) {
8159 PrefsAccount
*ac
= (PrefsAccount
*)accounts
->data
;
8160 gchar
*name
, *from
= NULL
;
8162 if (ac
== compose
->account
) def_menu
= num
;
8164 name
= g_markup_printf_escaped("<i>%s</i>",
8167 if (ac
== compose
->account
) {
8168 if (ac
->name
&& *ac
->name
) {
8170 QUOTE_IF_REQUIRED_NORMAL(buf
, ac
->name
, return NULL
);
8171 from
= g_strdup_printf("%s <%s>",
8173 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
8175 from
= g_strdup_printf("%s",
8177 gtk_entry_set_text(GTK_ENTRY(from_name
), from
);
8180 COMBOBOX_ADD(menu
, name
, ac
->account_id
);
8185 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu
), def_menu
);
8187 g_signal_connect(G_OBJECT(optmenu
), "changed",
8188 G_CALLBACK(account_activated
),
8190 g_signal_connect(G_OBJECT(from_name
), "populate-popup",
8191 G_CALLBACK(compose_entry_popup_extend
),
8194 fromlabel
= gtk_label_new_with_mnemonic(_("_From:"));
8195 gtk_label_set_mnemonic_widget(GTK_LABEL(fromlabel
), from_name
);
8197 gtk_box_pack_start(GTK_BOX(hbox
), fromlabel
, FALSE
, FALSE
, 4);
8198 gtk_box_pack_start(GTK_BOX(hbox
), optmenubox
, FALSE
, FALSE
, 0);
8199 gtk_box_pack_start(GTK_BOX(hbox
), from_name
, TRUE
, TRUE
, 0);
8201 /* Putting only the GtkEntry into focus chain of parent hbox causes
8202 * the account selector combobox next to it to be unreachable when
8203 * navigating widgets in GtkTable with up/down arrow keys.
8204 * Note: gtk_widget_set_can_focus() was not enough. */
8206 l
= g_list_prepend(l
, from_name
);
8207 gtk_container_set_focus_chain(GTK_CONTAINER(hbox
), l
);
8210 CLAWS_SET_TIP(optmenubox
,
8211 _("Account to use for this email"));
8212 CLAWS_SET_TIP(from_name
,
8213 _("Sender address to be used"));
8215 compose
->account_combo
= optmenu
;
8216 compose
->from_name
= from_name
;
8221 static void compose_set_priority_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
8223 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
8224 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
8225 Compose
*compose
= (Compose
*) data
;
8227 compose
->priority
= value
;
8231 static void compose_reply_change_mode(Compose
*compose
,
8234 gboolean was_modified
= compose
->modified
;
8236 gboolean all
= FALSE
, ml
= FALSE
, sender
= FALSE
, followup
= FALSE
;
8238 cm_return_if_fail(compose
->replyinfo
!= NULL
);
8240 if (action
== COMPOSE_REPLY
&& prefs_common
.default_reply_list
)
8242 if (action
== COMPOSE_REPLY
&& compose
->rmode
== COMPOSE_FOLLOWUP_AND_REPLY_TO
)
8244 if (action
== COMPOSE_REPLY_TO_ALL
)
8246 if (action
== COMPOSE_REPLY_TO_SENDER
)
8248 if (action
== COMPOSE_REPLY_TO_LIST
)
8251 compose_remove_header_entries(compose
);
8252 compose_reply_set_entry(compose
, compose
->replyinfo
, all
, ml
, sender
, followup
);
8253 if (compose
->account
->set_autocc
&& compose
->account
->auto_cc
)
8254 compose_entry_append(compose
, compose
->account
->auto_cc
, COMPOSE_CC
, PREF_ACCOUNT
);
8256 if (compose
->account
->set_autobcc
&& compose
->account
->auto_bcc
)
8257 compose_entry_append(compose
, compose
->account
->auto_bcc
, COMPOSE_BCC
, PREF_ACCOUNT
);
8259 if (compose
->account
->set_autoreplyto
&& compose
->account
->auto_replyto
)
8260 compose_entry_append(compose
, compose
->account
->auto_replyto
, COMPOSE_REPLYTO
, PREF_ACCOUNT
);
8261 compose_show_first_last_header(compose
, TRUE
);
8262 compose
->modified
= was_modified
;
8263 compose_set_title(compose
);
8266 static void compose_reply_change_mode_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
8268 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
8269 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
8270 Compose
*compose
= (Compose
*) data
;
8273 compose_reply_change_mode(compose
, value
);
8276 static void compose_update_priority_menu_item(Compose
* compose
)
8278 GtkWidget
*menuitem
= NULL
;
8279 switch (compose
->priority
) {
8280 case PRIORITY_HIGHEST
:
8281 menuitem
= gtk_ui_manager_get_widget
8282 (compose
->ui_manager
, "/Menu/Options/Priority/Highest");
8285 menuitem
= gtk_ui_manager_get_widget
8286 (compose
->ui_manager
, "/Menu/Options/Priority/High");
8288 case PRIORITY_NORMAL
:
8289 menuitem
= gtk_ui_manager_get_widget
8290 (compose
->ui_manager
, "/Menu/Options/Priority/Normal");
8293 menuitem
= gtk_ui_manager_get_widget
8294 (compose
->ui_manager
, "/Menu/Options/Priority/Low");
8296 case PRIORITY_LOWEST
:
8297 menuitem
= gtk_ui_manager_get_widget
8298 (compose
->ui_manager
, "/Menu/Options/Priority/Lowest");
8301 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), TRUE
);
8304 static void compose_set_privacy_system_cb(GtkWidget
*widget
, gpointer data
)
8306 Compose
*compose
= (Compose
*) data
;
8308 gboolean can_sign
= FALSE
, can_encrypt
= FALSE
;
8310 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget
));
8312 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget
)))
8315 systemid
= g_object_get_data(G_OBJECT(widget
), "privacy_system");
8316 g_free(compose
->privacy_system
);
8317 compose
->privacy_system
= NULL
;
8318 g_free(compose
->encdata
);
8319 compose
->encdata
= NULL
;
8320 if (systemid
!= NULL
) {
8321 compose
->privacy_system
= g_strdup(systemid
);
8323 can_sign
= privacy_system_can_sign(systemid
);
8324 can_encrypt
= privacy_system_can_encrypt(systemid
);
8327 debug_print("activated privacy system: %s\n", systemid
!= NULL
? systemid
: "None");
8329 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Sign", can_sign
);
8330 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Encrypt", can_encrypt
);
8333 static void compose_update_privacy_system_menu_item(Compose
* compose
, gboolean warn
)
8335 static gchar
*branch_path
= "/Menu/Options/PrivacySystem";
8336 GtkWidget
*menuitem
= NULL
;
8337 GList
*children
, *amenu
;
8338 gboolean can_sign
= FALSE
, can_encrypt
= FALSE
;
8339 gboolean found
= FALSE
;
8341 if (compose
->privacy_system
!= NULL
) {
8343 menuitem
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8344 gtk_ui_manager_get_widget(compose
->ui_manager
, branch_path
)));
8345 cm_return_if_fail(menuitem
!= NULL
);
8347 children
= gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem
)));
8350 while (amenu
!= NULL
) {
8351 systemid
= g_object_get_data(G_OBJECT(amenu
->data
), "privacy_system");
8352 if (systemid
!= NULL
) {
8353 if (strcmp(systemid
, compose
->privacy_system
) == 0 &&
8354 GTK_IS_CHECK_MENU_ITEM(amenu
->data
)) {
8355 menuitem
= GTK_WIDGET(amenu
->data
);
8357 can_sign
= privacy_system_can_sign(systemid
);
8358 can_encrypt
= privacy_system_can_encrypt(systemid
);
8362 } else if (strlen(compose
->privacy_system
) == 0 &&
8363 GTK_IS_CHECK_MENU_ITEM(amenu
->data
)) {
8364 menuitem
= GTK_WIDGET(amenu
->data
);
8367 can_encrypt
= FALSE
;
8372 amenu
= amenu
->next
;
8374 g_list_free(children
);
8375 if (menuitem
!= NULL
)
8376 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem
), TRUE
);
8378 if (warn
&& !found
&& strlen(compose
->privacy_system
)) {
8379 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8380 "will not be able to sign or encrypt this message."),
8381 compose
->privacy_system
);
8385 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Sign", can_sign
);
8386 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options/Encrypt", can_encrypt
);
8389 static void compose_set_out_encoding(Compose
*compose
)
8391 CharSet out_encoding
;
8392 const gchar
*branch
= NULL
;
8393 out_encoding
= conv_get_charset_from_str(prefs_common
.outgoing_charset
);
8395 switch(out_encoding
) {
8396 case C_AUTO
: branch
= "Menu/Options/Encoding/" CS_AUTO
; break;
8397 case C_US_ASCII
: branch
= "Menu/Options/Encoding/" CS_US_ASCII
; break;
8398 case C_UTF_8
: branch
= "Menu/Options/Encoding/" CS_UTF_8
; break;
8399 case C_ISO_8859_2
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_2
; break;
8400 case C_ISO_8859_7
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_7
; break;
8401 case C_ISO_8859_9
: branch
= "Menu/Options/Encoding/" CS_ISO_8859_9
; break;
8402 case C_ISO_8859_1
: branch
= "Menu/Options/Encoding/Western/" CS_ISO_8859_1
; break;
8403 case C_ISO_8859_15
: branch
= "Menu/Options/Encoding/Western/" CS_ISO_8859_15
; break;
8404 case C_WINDOWS_1252
: branch
= "Menu/Options/Encoding/Western/" CS_WINDOWS_1252
; break;
8405 case C_ISO_8859_13
: branch
= "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13
; break;
8406 case C_ISO_8859_4
: branch
= "Menu/Options/Encoding/Baltic" CS_ISO_8859_4
; break;
8407 case C_ISO_8859_8
: branch
= "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8
; break;
8408 case C_WINDOWS_1255
: branch
= "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255
; break;
8409 case C_ISO_8859_6
: branch
= "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6
; break;
8410 case C_WINDOWS_1256
: branch
= "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256
; break;
8411 case C_ISO_8859_5
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5
; break;
8412 case C_KOI8_R
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R
; break;
8413 case C_MACCYR
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_MACCYR
; break;
8414 case C_KOI8_U
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U
; break;
8415 case C_WINDOWS_1251
: branch
= "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251
; break;
8416 case C_ISO_2022_JP
: branch
= "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP
; break;
8417 case C_ISO_2022_JP_2
: branch
= "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2
; break;
8418 case C_EUC_JP
: branch
= "Menu/Options/Encoding/Japanese/" CS_EUC_JP
; break;
8419 case C_SHIFT_JIS
: branch
= "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS
; break;
8420 case C_GB18030
: branch
= "Menu/Options/Encoding/Chinese/" CS_GB18030
; break;
8421 case C_GB2312
: branch
= "Menu/Options/Encoding/Chinese/" CS_GB2312
; break;
8422 case C_GBK
: branch
= "Menu/Options/Encoding/Chinese/" CS_GBK
; break;
8423 case C_BIG5
: branch
= "Menu/Options/Encoding/Chinese/" CS_BIG5
; break;
8424 case C_EUC_TW
: branch
= "Menu/Options/Encoding/Chinese/" CS_EUC_TW
; break;
8425 case C_EUC_KR
: branch
= "Menu/Options/Encoding/Korean/" CS_EUC_KR
; break;
8426 case C_ISO_2022_KR
: branch
= "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR
; break;
8427 case C_TIS_620
: branch
= "Menu/Options/Encoding/Thai/" CS_TIS_620
; break;
8428 case C_WINDOWS_874
: branch
= "Menu/Options/Encoding/Thai/" CS_WINDOWS_874
; break;
8429 default: branch
= "Menu/Options/Encoding/" CS_AUTO
; break;
8431 cm_toggle_menu_set_active_full(compose
->ui_manager
, (gchar
*)branch
, TRUE
);
8434 static void compose_set_template_menu(Compose
*compose
)
8436 GSList
*tmpl_list
, *cur
;
8440 tmpl_list
= template_get_config();
8442 menu
= gtk_menu_new();
8444 gtk_menu_set_accel_group (GTK_MENU (menu
),
8445 gtk_ui_manager_get_accel_group(compose
->ui_manager
));
8446 for (cur
= tmpl_list
; cur
!= NULL
; cur
= cur
->next
) {
8447 Template
*tmpl
= (Template
*)cur
->data
;
8448 gchar
*accel_path
= NULL
;
8449 item
= gtk_menu_item_new_with_label(tmpl
->name
);
8450 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
8451 g_signal_connect(G_OBJECT(item
), "activate",
8452 G_CALLBACK(compose_template_activate_cb
),
8454 g_object_set_data(G_OBJECT(item
), "template", tmpl
);
8455 gtk_widget_show(item
);
8456 accel_path
= g_strconcat("<ComposeTemplates>" , "/", tmpl
->name
, NULL
);
8457 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item
), accel_path
);
8461 gtk_widget_show(menu
);
8462 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose
->tmpl_menu
), menu
);
8465 void compose_update_actions_menu(Compose
*compose
)
8467 action_update_compose_menu(compose
->ui_manager
, "/Menu/Tools/Actions", compose
);
8470 static void compose_update_privacy_systems_menu(Compose
*compose
)
8472 static gchar
*branch_path
= "/Menu/Options/PrivacySystem";
8473 GSList
*systems
, *cur
;
8475 GtkWidget
*system_none
;
8477 GtkWidget
*privacy_menuitem
= gtk_ui_manager_get_widget(compose
->ui_manager
, branch_path
);
8478 GtkWidget
*privacy_menu
= gtk_menu_new();
8480 system_none
= gtk_radio_menu_item_new_with_mnemonic(NULL
, _("_None"));
8481 g_object_set_data_full(G_OBJECT(system_none
), "privacy_system", NULL
, NULL
);
8483 g_signal_connect(G_OBJECT(system_none
), "activate",
8484 G_CALLBACK(compose_set_privacy_system_cb
), compose
);
8486 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu
), system_none
);
8487 gtk_widget_show(system_none
);
8489 systems
= privacy_get_system_ids();
8490 for (cur
= systems
; cur
!= NULL
; cur
= g_slist_next(cur
)) {
8491 gchar
*systemid
= cur
->data
;
8493 group
= gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none
));
8494 widget
= gtk_radio_menu_item_new_with_label(group
,
8495 privacy_system_get_name(systemid
));
8496 g_object_set_data_full(G_OBJECT(widget
), "privacy_system",
8497 g_strdup(systemid
), g_free
);
8498 g_signal_connect(G_OBJECT(widget
), "activate",
8499 G_CALLBACK(compose_set_privacy_system_cb
), compose
);
8501 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu
), widget
);
8502 gtk_widget_show(widget
);
8505 g_slist_free(systems
);
8506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem
), privacy_menu
);
8507 gtk_widget_show_all(privacy_menu
);
8508 gtk_widget_show_all(privacy_menuitem
);
8511 void compose_reflect_prefs_all(void)
8516 for (cur
= compose_list
; cur
!= NULL
; cur
= cur
->next
) {
8517 compose
= (Compose
*)cur
->data
;
8518 compose_set_template_menu(compose
);
8522 void compose_reflect_prefs_pixmap_theme(void)
8527 for (cur
= compose_list
; cur
!= NULL
; cur
= cur
->next
) {
8528 compose
= (Compose
*)cur
->data
;
8529 toolbar_update(TOOLBAR_COMPOSE
, compose
);
8533 static const gchar
*compose_quote_char_from_context(Compose
*compose
)
8535 const gchar
*qmark
= NULL
;
8537 cm_return_val_if_fail(compose
!= NULL
, NULL
);
8539 switch (compose
->mode
) {
8540 /* use forward-specific quote char */
8541 case COMPOSE_FORWARD
:
8542 case COMPOSE_FORWARD_AS_ATTACH
:
8543 case COMPOSE_FORWARD_INLINE
:
8544 if (compose
->folder
&& compose
->folder
->prefs
&&
8545 compose
->folder
->prefs
->forward_with_format
)
8546 qmark
= compose
->folder
->prefs
->forward_quotemark
;
8547 else if (compose
->account
->forward_with_format
)
8548 qmark
= compose
->account
->forward_quotemark
;
8550 qmark
= prefs_common
.fw_quotemark
;
8553 /* use reply-specific quote char in all other modes */
8555 if (compose
->folder
&& compose
->folder
->prefs
&&
8556 compose
->folder
->prefs
->reply_with_format
)
8557 qmark
= compose
->folder
->prefs
->reply_quotemark
;
8558 else if (compose
->account
->reply_with_format
)
8559 qmark
= compose
->account
->reply_quotemark
;
8561 qmark
= prefs_common
.quotemark
;
8565 if (qmark
== NULL
|| *qmark
== '\0')
8571 static void compose_template_apply(Compose
*compose
, Template
*tmpl
,
8575 GtkTextBuffer
*buffer
;
8579 gchar
*parsed_str
= NULL
;
8580 gint cursor_pos
= 0;
8581 const gchar
*err_msg
= _("The body of the template has an error at line %d.");
8584 /* process the body */
8586 text
= GTK_TEXT_VIEW(compose
->text
);
8587 buffer
= gtk_text_view_get_buffer(text
);
8590 qmark
= compose_quote_char_from_context(compose
);
8592 if (compose
->replyinfo
!= NULL
) {
8595 gtk_text_buffer_set_text(buffer
, "", -1);
8596 mark
= gtk_text_buffer_get_insert(buffer
);
8597 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8599 parsed_str
= compose_quote_fmt(compose
, compose
->replyinfo
,
8600 tmpl
->value
, qmark
, NULL
, FALSE
, FALSE
, err_msg
);
8602 } else if (compose
->fwdinfo
!= NULL
) {
8605 gtk_text_buffer_set_text(buffer
, "", -1);
8606 mark
= gtk_text_buffer_get_insert(buffer
);
8607 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8609 parsed_str
= compose_quote_fmt(compose
, compose
->fwdinfo
,
8610 tmpl
->value
, qmark
, NULL
, FALSE
, FALSE
, err_msg
);
8613 MsgInfo
* dummyinfo
= compose_msginfo_new_from_compose(compose
);
8615 GtkTextIter start
, end
;
8618 gtk_text_buffer_get_start_iter(buffer
, &start
);
8619 gtk_text_buffer_get_iter_at_offset(buffer
, &end
, -1);
8620 tmp
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
8622 /* clear the buffer now */
8624 gtk_text_buffer_set_text(buffer
, "", -1);
8626 parsed_str
= compose_quote_fmt(compose
, dummyinfo
,
8627 tmpl
->value
, qmark
, tmp
, FALSE
, FALSE
, err_msg
);
8628 procmsg_msginfo_free( &dummyinfo
);
8634 gtk_text_buffer_set_text(buffer
, "", -1);
8635 mark
= gtk_text_buffer_get_insert(buffer
);
8636 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
8639 if (replace
&& parsed_str
&& compose
->account
->auto_sig
)
8640 compose_insert_sig(compose
, FALSE
);
8642 if (replace
&& parsed_str
) {
8643 gtk_text_buffer_get_start_iter(buffer
, &iter
);
8644 gtk_text_buffer_place_cursor(buffer
, &iter
);
8648 cursor_pos
= quote_fmt_get_cursor_pos();
8649 compose
->set_cursor_pos
= cursor_pos
;
8650 if (cursor_pos
== -1)
8652 gtk_text_buffer_get_start_iter(buffer
, &iter
);
8653 gtk_text_buffer_get_iter_at_offset(buffer
, &iter
, cursor_pos
);
8654 gtk_text_buffer_place_cursor(buffer
, &iter
);
8657 /* process the other fields */
8659 compose_template_apply_fields(compose
, tmpl
);
8660 compose_attach_from_list(compose
, quote_fmt_get_attachments_list(), FALSE
);
8661 quote_fmt_reset_vartable();
8662 compose_changed_cb(NULL
, compose
);
8665 if (compose
->gtkaspell
&& compose
->gtkaspell
->check_while_typing
)
8666 gtkaspell_highlight_all(compose
->gtkaspell
);
8670 static void compose_template_apply_fields(Compose
*compose
, Template
*tmpl
)
8672 MsgInfo
* dummyinfo
= NULL
;
8673 MsgInfo
*msginfo
= NULL
;
8676 if (compose
->replyinfo
!= NULL
)
8677 msginfo
= compose
->replyinfo
;
8678 else if (compose
->fwdinfo
!= NULL
)
8679 msginfo
= compose
->fwdinfo
;
8681 dummyinfo
= compose_msginfo_new_from_compose(compose
);
8682 msginfo
= dummyinfo
;
8685 if (tmpl
->from
&& *tmpl
->from
!= '\0') {
8687 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8688 compose
->gtkaspell
);
8690 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8692 quote_fmt_scan_string(tmpl
->from
);
8695 buf
= quote_fmt_get_buffer();
8697 alertpanel_error(_("Template From format error."));
8699 gtk_entry_set_text(GTK_ENTRY(compose
->from_name
), buf
);
8703 if (tmpl
->to
&& *tmpl
->to
!= '\0') {
8705 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8706 compose
->gtkaspell
);
8708 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8710 quote_fmt_scan_string(tmpl
->to
);
8713 buf
= quote_fmt_get_buffer();
8715 alertpanel_error(_("Template To format error."));
8717 compose_entry_append(compose
, buf
, COMPOSE_TO
, PREF_TEMPLATE
);
8721 if (tmpl
->cc
&& *tmpl
->cc
!= '\0') {
8723 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8724 compose
->gtkaspell
);
8726 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8728 quote_fmt_scan_string(tmpl
->cc
);
8731 buf
= quote_fmt_get_buffer();
8733 alertpanel_error(_("Template Cc format error."));
8735 compose_entry_append(compose
, buf
, COMPOSE_CC
, PREF_TEMPLATE
);
8739 if (tmpl
->bcc
&& *tmpl
->bcc
!= '\0') {
8741 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8742 compose
->gtkaspell
);
8744 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8746 quote_fmt_scan_string(tmpl
->bcc
);
8749 buf
= quote_fmt_get_buffer();
8751 alertpanel_error(_("Template Bcc format error."));
8753 compose_entry_append(compose
, buf
, COMPOSE_BCC
, PREF_TEMPLATE
);
8757 if (tmpl
->replyto
&& *tmpl
->replyto
!= '\0') {
8759 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8760 compose
->gtkaspell
);
8762 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8764 quote_fmt_scan_string(tmpl
->replyto
);
8767 buf
= quote_fmt_get_buffer();
8769 alertpanel_error(_("Template Reply-To format error."));
8771 compose_entry_append(compose
, buf
, COMPOSE_REPLYTO
, PREF_TEMPLATE
);
8775 /* process the subject */
8776 if (tmpl
->subject
&& *tmpl
->subject
!= '\0') {
8778 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
,
8779 compose
->gtkaspell
);
8781 quote_fmt_init(msginfo
, NULL
, NULL
, FALSE
, compose
->account
, FALSE
);
8783 quote_fmt_scan_string(tmpl
->subject
);
8786 buf
= quote_fmt_get_buffer();
8788 alertpanel_error(_("Template subject format error."));
8790 gtk_entry_set_text(GTK_ENTRY(compose
->subject_entry
), buf
);
8794 procmsg_msginfo_free( &dummyinfo
);
8797 static void compose_destroy(Compose
*compose
)
8799 GtkAllocation allocation
;
8800 GtkTextBuffer
*buffer
;
8801 GtkClipboard
*clipboard
;
8803 compose_list
= g_list_remove(compose_list
, compose
);
8805 if (compose
->updating
) {
8806 debug_print("danger, not destroying anything now\n");
8807 compose
->deferred_destroy
= TRUE
;
8811 /* NOTE: address_completion_end() does nothing with the window
8812 * however this may change. */
8813 address_completion_end(compose
->window
);
8815 slist_free_strings_full(compose
->to_list
);
8816 slist_free_strings_full(compose
->newsgroup_list
);
8817 slist_free_strings_full(compose
->header_list
);
8819 slist_free_strings_full(extra_headers
);
8820 extra_headers
= NULL
;
8822 compose
->header_list
= compose
->newsgroup_list
= compose
->to_list
= NULL
;
8824 g_hash_table_destroy(compose
->email_hashtable
);
8826 hooks_unregister_hook(FOLDER_UPDATE_HOOKLIST
,
8827 compose
->folder_update_callback_id
);
8829 procmsg_msginfo_free(&(compose
->targetinfo
));
8830 procmsg_msginfo_free(&(compose
->replyinfo
));
8831 procmsg_msginfo_free(&(compose
->fwdinfo
));
8833 g_free(compose
->replyto
);
8834 g_free(compose
->cc
);
8835 g_free(compose
->bcc
);
8836 g_free(compose
->newsgroups
);
8837 g_free(compose
->followup_to
);
8839 g_free(compose
->ml_post
);
8841 g_free(compose
->inreplyto
);
8842 g_free(compose
->references
);
8843 g_free(compose
->msgid
);
8844 g_free(compose
->boundary
);
8846 g_free(compose
->redirect_filename
);
8847 if (compose
->undostruct
)
8848 undo_destroy(compose
->undostruct
);
8850 g_free(compose
->sig_str
);
8852 g_free(compose
->exteditor_file
);
8854 g_free(compose
->orig_charset
);
8856 g_free(compose
->privacy_system
);
8857 g_free(compose
->encdata
);
8859 #ifndef USE_ALT_ADDRBOOK
8860 if (addressbook_get_target_compose() == compose
)
8861 addressbook_set_target_compose(NULL
);
8864 if (compose
->gtkaspell
) {
8865 gtkaspell_delete(compose
->gtkaspell
);
8866 compose
->gtkaspell
= NULL
;
8870 if (!compose
->batch
) {
8871 gtk_widget_get_allocation(compose
->window
, &allocation
);
8872 prefs_common
.compose_width
= allocation
.width
;
8873 prefs_common
.compose_height
= allocation
.height
;
8876 if (!gtk_widget_get_parent(compose
->paned
))
8877 gtk_widget_destroy(compose
->paned
);
8878 gtk_widget_destroy(compose
->popupmenu
);
8880 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
8881 clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
8882 gtk_text_buffer_remove_selection_clipboard(buffer
, clipboard
);
8884 gtk_widget_destroy(compose
->window
);
8885 toolbar_destroy(compose
->toolbar
);
8886 g_free(compose
->toolbar
);
8887 cm_mutex_free(compose
->mutex
);
8891 static void compose_attach_info_free(AttachInfo
*ainfo
)
8893 g_free(ainfo
->file
);
8894 g_free(ainfo
->content_type
);
8895 g_free(ainfo
->name
);
8896 g_free(ainfo
->charset
);
8900 static void compose_attach_update_label(Compose
*compose
)
8905 GtkTreeModel
*model
;
8910 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(compose
->attach_clist
));
8911 if(!gtk_tree_model_get_iter_first(model
, &iter
)) {
8912 gtk_label_set_text(GTK_LABEL(compose
->attach_label
), "");
8916 while(gtk_tree_model_iter_next(model
, &iter
))
8919 text
= g_strdup_printf("(%d)", i
);
8920 gtk_label_set_text(GTK_LABEL(compose
->attach_label
), text
);
8924 static void compose_attach_remove_selected(GtkAction
*action
, gpointer data
)
8926 Compose
*compose
= (Compose
*)data
;
8927 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
8928 GtkTreeSelection
*selection
;
8930 GtkTreeModel
*model
;
8932 selection
= gtk_tree_view_get_selection(tree_view
);
8933 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
8938 for (cur
= sel
; cur
!= NULL
; cur
= cur
->next
) {
8939 GtkTreePath
*path
= cur
->data
;
8940 GtkTreeRowReference
*ref
= gtk_tree_row_reference_new
8943 gtk_tree_path_free(path
);
8946 for (cur
= sel
; cur
!= NULL
; cur
= cur
->next
) {
8947 GtkTreeRowReference
*ref
= cur
->data
;
8948 GtkTreePath
*path
= gtk_tree_row_reference_get_path(ref
);
8951 if (gtk_tree_model_get_iter(model
, &iter
, path
))
8952 gtk_list_store_remove(GTK_LIST_STORE(model
), &iter
);
8954 gtk_tree_path_free(path
);
8955 gtk_tree_row_reference_free(ref
);
8959 compose_attach_update_label(compose
);
8962 static struct _AttachProperty
8965 GtkWidget
*mimetype_entry
;
8966 GtkWidget
*encoding_optmenu
;
8967 GtkWidget
*path_entry
;
8968 GtkWidget
*filename_entry
;
8970 GtkWidget
*cancel_btn
;
8973 static void gtk_tree_path_free_(gpointer ptr
, gpointer data
)
8975 gtk_tree_path_free((GtkTreePath
*)ptr
);
8978 static void compose_attach_property(GtkAction
*action
, gpointer data
)
8980 Compose
*compose
= (Compose
*)data
;
8981 GtkTreeView
*tree_view
= GTK_TREE_VIEW(compose
->attach_clist
);
8983 GtkComboBox
*optmenu
;
8984 GtkTreeSelection
*selection
;
8986 GtkTreeModel
*model
;
8989 static gboolean cancelled
;
8991 /* only if one selected */
8992 selection
= gtk_tree_view_get_selection(tree_view
);
8993 if (gtk_tree_selection_count_selected_rows(selection
) != 1)
8996 sel
= gtk_tree_selection_get_selected_rows(selection
, &model
);
9000 path
= (GtkTreePath
*) sel
->data
;
9001 gtk_tree_model_get_iter(model
, &iter
, path
);
9002 gtk_tree_model_get(model
, &iter
, COL_DATA
, &ainfo
, -1);
9005 g_list_foreach(sel
, gtk_tree_path_free_
, NULL
);
9011 if (!attach_prop
.window
)
9012 compose_attach_property_create(&cancelled
);
9013 gtk_window_set_modal(GTK_WINDOW(attach_prop
.window
), TRUE
);
9014 gtk_widget_grab_focus(attach_prop
.ok_btn
);
9015 gtk_widget_show(attach_prop
.window
);
9016 gtk_window_set_transient_for(GTK_WINDOW(attach_prop
.window
),
9017 GTK_WINDOW(compose
->window
));
9019 optmenu
= GTK_COMBO_BOX(attach_prop
.encoding_optmenu
);
9020 if (ainfo
->encoding
== ENC_UNKNOWN
)
9021 combobox_select_by_data(optmenu
, ENC_BASE64
);
9023 combobox_select_by_data(optmenu
, ainfo
->encoding
);
9025 gtk_entry_set_text(GTK_ENTRY(attach_prop
.mimetype_entry
),
9026 ainfo
->content_type
? ainfo
->content_type
: "");
9027 gtk_entry_set_text(GTK_ENTRY(attach_prop
.path_entry
),
9028 ainfo
->file
? ainfo
->file
: "");
9029 gtk_entry_set_text(GTK_ENTRY(attach_prop
.filename_entry
),
9030 ainfo
->name
? ainfo
->name
: "");
9033 const gchar
*entry_text
;
9035 gchar
*cnttype
= NULL
;
9042 gtk_widget_hide(attach_prop
.window
);
9043 gtk_window_set_modal(GTK_WINDOW(attach_prop
.window
), FALSE
);
9048 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.mimetype_entry
));
9049 if (*entry_text
!= '\0') {
9052 text
= g_strstrip(g_strdup(entry_text
));
9053 if ((p
= strchr(text
, '/')) && !strchr(p
+ 1, '/')) {
9054 cnttype
= g_strdup(text
);
9057 alertpanel_error(_("Invalid MIME type."));
9063 ainfo
->encoding
= combobox_get_active_data(optmenu
);
9065 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.path_entry
));
9066 if (*entry_text
!= '\0') {
9067 if (is_file_exist(entry_text
) &&
9068 (size
= get_file_size(entry_text
)) > 0)
9069 file
= g_strdup(entry_text
);
9072 (_("File doesn't exist or is empty."));
9078 entry_text
= gtk_entry_get_text(GTK_ENTRY(attach_prop
.filename_entry
));
9079 if (*entry_text
!= '\0') {
9080 g_free(ainfo
->name
);
9081 ainfo
->name
= g_strdup(entry_text
);
9085 g_free(ainfo
->content_type
);
9086 ainfo
->content_type
= cnttype
;
9089 g_free(ainfo
->file
);
9093 ainfo
->size
= (goffset
)size
;
9095 /* update tree store */
9096 text
= to_human_readable(ainfo
->size
);
9097 gtk_tree_model_get_iter(model
, &iter
, path
);
9098 gtk_list_store_set(GTK_LIST_STORE(model
), &iter
,
9099 COL_MIMETYPE
, ainfo
->content_type
,
9101 COL_NAME
, ainfo
->name
,
9102 COL_CHARSET
, ainfo
->charset
,
9108 gtk_tree_path_free(path
);
9111 #define SET_LABEL_AND_ENTRY(str, entry, top) \
9113 label = gtk_label_new(str); \
9114 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
9115 GTK_FILL, 0, 0, 0); \
9116 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
9118 entry = gtk_entry_new(); \
9119 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
9120 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
9123 static void compose_attach_property_create(gboolean
*cancelled
)
9129 GtkWidget
*mimetype_entry
;
9132 GtkListStore
*optmenu_menu
;
9133 GtkWidget
*path_entry
;
9134 GtkWidget
*filename_entry
;
9137 GtkWidget
*cancel_btn
;
9138 GList
*mime_type_list
, *strlist
;
9141 debug_print("Creating attach_property window...\n");
9143 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "compose_attach_property");
9144 gtk_widget_set_size_request(window
, 480, -1);
9145 gtk_container_set_border_width(GTK_CONTAINER(window
), 8);
9146 gtk_window_set_title(GTK_WINDOW(window
), _("Properties"));
9147 gtk_window_set_position(GTK_WINDOW(window
), GTK_WIN_POS_CENTER
);
9148 g_signal_connect(G_OBJECT(window
), "delete_event",
9149 G_CALLBACK(attach_property_delete_event
),
9151 g_signal_connect(G_OBJECT(window
), "key_press_event",
9152 G_CALLBACK(attach_property_key_pressed
),
9155 vbox
= gtk_vbox_new(FALSE
, 8);
9156 gtk_container_add(GTK_CONTAINER(window
), vbox
);
9158 table
= gtk_table_new(4, 2, FALSE
);
9159 gtk_box_pack_start(GTK_BOX(vbox
), table
, FALSE
, FALSE
, 0);
9160 gtk_table_set_row_spacings(GTK_TABLE(table
), 8);
9161 gtk_table_set_col_spacings(GTK_TABLE(table
), 8);
9163 label
= gtk_label_new(_("MIME type"));
9164 gtk_table_attach(GTK_TABLE(table
), label
, 0, 1, 0, (0 + 1),
9166 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
9167 #if !GTK_CHECK_VERSION(2, 24, 0)
9168 mimetype_entry
= gtk_combo_box_entry_new_text();
9170 mimetype_entry
= gtk_combo_box_text_new_with_entry();
9172 gtk_table_attach(GTK_TABLE(table
), mimetype_entry
, 1, 2, 0, (0 + 1),
9173 GTK_EXPAND
|GTK_SHRINK
|GTK_FILL
, 0, 0, 0);
9175 /* stuff with list */
9176 mime_type_list
= procmime_get_mime_type_list();
9178 for (; mime_type_list
!= NULL
; mime_type_list
= mime_type_list
->next
) {
9179 MimeType
*type
= (MimeType
*) mime_type_list
->data
;
9182 tmp
= g_strdup_printf("%s/%s", type
->type
, type
->sub_type
);
9184 if (g_list_find_custom(strlist
, tmp
, (GCompareFunc
)strcmp2
))
9187 strlist
= g_list_insert_sorted(strlist
, (gpointer
)tmp
,
9188 (GCompareFunc
)strcmp2
);
9191 for (mime_type_list
= strlist
; mime_type_list
!= NULL
;
9192 mime_type_list
= mime_type_list
->next
) {
9193 #if !GTK_CHECK_VERSION(2, 24, 0)
9194 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry
), mime_type_list
->data
);
9196 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry
), mime_type_list
->data
);
9198 g_free(mime_type_list
->data
);
9200 g_list_free(strlist
);
9201 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry
), 0);
9202 mimetype_entry
= gtk_bin_get_child(GTK_BIN((mimetype_entry
)));
9204 label
= gtk_label_new(_("Encoding"));
9205 gtk_table_attach(GTK_TABLE(table
), label
, 0, 1, 1, 2,
9207 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
9209 hbox
= gtk_hbox_new(FALSE
, 0);
9210 gtk_table_attach(GTK_TABLE(table
), hbox
, 1, 2, 1, 2,
9211 GTK_EXPAND
|GTK_SHRINK
|GTK_FILL
, 0, 0, 0);
9213 optmenu
= gtkut_sc_combobox_create(NULL
, TRUE
);
9214 optmenu_menu
= GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu
)));
9216 COMBOBOX_ADD(optmenu_menu
, "7bit", ENC_7BIT
);
9217 COMBOBOX_ADD(optmenu_menu
, "8bit", ENC_8BIT
);
9218 COMBOBOX_ADD(optmenu_menu
, "quoted-printable", ENC_QUOTED_PRINTABLE
);
9219 COMBOBOX_ADD(optmenu_menu
, "base64", ENC_BASE64
);
9220 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu
), 0);
9222 gtk_box_pack_start(GTK_BOX(hbox
), optmenu
, TRUE
, TRUE
, 0);
9224 SET_LABEL_AND_ENTRY(_("Path"), path_entry
, 2);
9225 SET_LABEL_AND_ENTRY(_("File name"), filename_entry
, 3);
9227 gtkut_stock_button_set_create(&hbbox
, &cancel_btn
, GTK_STOCK_CANCEL
,
9228 &ok_btn
, GTK_STOCK_OK
,
9230 gtk_box_pack_end(GTK_BOX(vbox
), hbbox
, FALSE
, FALSE
, 0);
9231 gtk_widget_grab_default(ok_btn
);
9233 g_signal_connect(G_OBJECT(ok_btn
), "clicked",
9234 G_CALLBACK(attach_property_ok
),
9236 g_signal_connect(G_OBJECT(cancel_btn
), "clicked",
9237 G_CALLBACK(attach_property_cancel
),
9240 gtk_widget_show_all(vbox
);
9242 attach_prop
.window
= window
;
9243 attach_prop
.mimetype_entry
= mimetype_entry
;
9244 attach_prop
.encoding_optmenu
= optmenu
;
9245 attach_prop
.path_entry
= path_entry
;
9246 attach_prop
.filename_entry
= filename_entry
;
9247 attach_prop
.ok_btn
= ok_btn
;
9248 attach_prop
.cancel_btn
= cancel_btn
;
9251 #undef SET_LABEL_AND_ENTRY
9253 static void attach_property_ok(GtkWidget
*widget
, gboolean
*cancelled
)
9259 static void attach_property_cancel(GtkWidget
*widget
, gboolean
*cancelled
)
9265 static gint
attach_property_delete_event(GtkWidget
*widget
, GdkEventAny
*event
,
9266 gboolean
*cancelled
)
9274 static gboolean
attach_property_key_pressed(GtkWidget
*widget
,
9276 gboolean
*cancelled
)
9278 if (event
&& event
->keyval
== GDK_KEY_Escape
) {
9282 if (event
&& event
->keyval
== GDK_KEY_Return
) {
9290 static void compose_exec_ext_editor(Compose
*compose
)
9295 GdkNativeWindow socket_wid
= 0;
9299 tmp
= g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9300 G_DIR_SEPARATOR
, compose
);
9302 if (compose_get_ext_editor_uses_socket()) {
9303 /* Only allow one socket */
9304 if (compose
->exteditor_socket
!= NULL
) {
9305 if (gtk_widget_is_focus(compose
->exteditor_socket
)) {
9306 /* Move the focus off of the socket */
9307 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9312 /* Create the receiving GtkSocket */
9313 socket
= gtk_socket_new ();
9314 g_signal_connect (GTK_OBJECT(socket
), "plug-removed",
9315 G_CALLBACK(compose_ext_editor_plug_removed_cb
),
9317 gtk_box_pack_start(GTK_BOX(compose
->edit_vbox
), socket
, TRUE
, TRUE
, 0);
9318 gtk_widget_set_size_request(socket
, prefs_common
.compose_width
, -1);
9319 /* Realize the socket so that we can use its ID */
9320 gtk_widget_realize(socket
);
9321 socket_wid
= gtk_socket_get_id(GTK_SOCKET (socket
));
9322 compose
->exteditor_socket
= socket
;
9325 if (pipe(pipe_fds
) < 0) {
9331 if ((pid
= fork()) < 0) {
9338 /* close the write side of the pipe */
9341 compose
->exteditor_file
= g_strdup(tmp
);
9342 compose
->exteditor_pid
= pid
;
9344 compose_set_ext_editor_sensitive(compose
, FALSE
);
9347 compose
->exteditor_ch
= g_io_channel_unix_new(pipe_fds
[0]);
9349 compose
->exteditor_ch
= g_io_channel_win32_new_fd(pipe_fds
[0]);
9351 compose
->exteditor_tag
= g_io_add_watch(compose
->exteditor_ch
,
9355 } else { /* process-monitoring process */
9361 /* close the read side of the pipe */
9364 if (compose_write_body_to_file(compose
, tmp
) < 0) {
9365 fd_write_all(pipe_fds
[1], "2\n", 2);
9369 pid_ed
= compose_exec_ext_editor_real(tmp
, socket_wid
);
9371 fd_write_all(pipe_fds
[1], "1\n", 2);
9375 /* wait until editor is terminated */
9376 waitpid(pid_ed
, NULL
, 0);
9378 fd_write_all(pipe_fds
[1], "0\n", 2);
9385 #endif /* G_OS_UNIX */
9389 static gboolean
compose_get_ext_editor_cmd_valid()
9391 gboolean has_s
= FALSE
;
9392 gboolean has_w
= FALSE
;
9393 const gchar
*p
= prefs_common_get_ext_editor_cmd();
9396 while ((p
= strchr(p
, '%'))) {
9402 } else if (*p
== 'w') {
9413 static gint
compose_exec_ext_editor_real(const gchar
*file
, GdkNativeWindow socket_wid
)
9420 cm_return_val_if_fail(file
!= NULL
, -1);
9422 if ((pid
= fork()) < 0) {
9427 if (pid
!= 0) return pid
;
9429 /* grandchild process */
9431 if (setpgid(0, getppid()))
9434 if (compose_get_ext_editor_cmd_valid()) {
9435 if (compose_get_ext_editor_uses_socket()) {
9436 p
= g_strdup(prefs_common_get_ext_editor_cmd());
9437 s
= strstr(p
, "%w");
9439 if (strstr(p
, "%s") < s
)
9440 g_snprintf(buf
, sizeof(buf
), p
, file
, socket_wid
);
9442 g_snprintf(buf
, sizeof(buf
), p
, socket_wid
, file
);
9445 g_snprintf(buf
, sizeof(buf
),
9446 prefs_common_get_ext_editor_cmd(), file
);
9449 if (prefs_common_get_ext_editor_cmd())
9450 g_warning("External editor command-line is invalid: '%s'",
9451 prefs_common_get_ext_editor_cmd());
9452 g_snprintf(buf
, sizeof(buf
), DEFAULT_EDITOR_CMD
, file
);
9455 cmdline
= strsplit_with_quote(buf
, " ", 1024);
9456 execvp(cmdline
[0], cmdline
);
9459 g_strfreev(cmdline
);
9464 static gboolean
compose_ext_editor_kill(Compose
*compose
)
9466 pid_t pgid
= compose
->exteditor_pid
* -1;
9469 ret
= kill(pgid
, 0);
9471 if (ret
== 0 || (ret
== -1 && EPERM
== errno
)) {
9475 msg
= g_strdup_printf
9476 (_("The external editor is still working.\n"
9477 "Force terminating the process?\n"
9478 "process group id: %d"), -pgid
);
9479 val
= alertpanel_full(_("Notice"), msg
, GTK_STOCK_NO
, GTK_STOCK_YES
,
9480 NULL
, FALSE
, NULL
, ALERT_WARNING
, G_ALERTDEFAULT
);
9484 if (val
== G_ALERTALTERNATE
) {
9485 g_source_remove(compose
->exteditor_tag
);
9486 g_io_channel_shutdown(compose
->exteditor_ch
,
9488 g_io_channel_unref(compose
->exteditor_ch
);
9490 if (kill(pgid
, SIGTERM
) < 0) perror("kill");
9491 waitpid(compose
->exteditor_pid
, NULL
, 0);
9493 g_warning("Terminated process group id: %d. "
9494 "Temporary file: %s", -pgid
, compose
->exteditor_file
);
9496 compose_set_ext_editor_sensitive(compose
, TRUE
);
9498 g_free(compose
->exteditor_file
);
9499 compose
->exteditor_file
= NULL
;
9500 compose
->exteditor_pid
= -1;
9501 compose
->exteditor_ch
= NULL
;
9502 compose
->exteditor_tag
= -1;
9510 static gboolean
compose_input_cb(GIOChannel
*source
, GIOCondition condition
,
9514 Compose
*compose
= (Compose
*)data
;
9517 debug_print("Compose: input from monitoring process\n");
9519 if (g_io_channel_read_chars(source
, buf
, sizeof(buf
), &bytes_read
, NULL
) != G_IO_STATUS_NORMAL
) {
9524 g_io_channel_shutdown(source
, FALSE
, NULL
);
9525 g_io_channel_unref(source
);
9527 waitpid(compose
->exteditor_pid
, NULL
, 0);
9529 if (buf
[0] == '0') { /* success */
9530 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
9531 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(text
);
9532 GtkTextIter start
, end
;
9535 gtk_text_buffer_set_text(buffer
, "", -1);
9536 compose_insert_file(compose
, compose
->exteditor_file
);
9537 compose_changed_cb(NULL
, compose
);
9538 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
9540 if (claws_unlink(compose
->exteditor_file
) < 0)
9541 FILE_OP_ERROR(compose
->exteditor_file
, "unlink");
9543 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose
->text
));
9544 gtk_text_buffer_get_start_iter(buffer
, &start
);
9545 gtk_text_buffer_get_end_iter(buffer
, &end
);
9546 chars
= gtk_text_buffer_get_text(buffer
, &start
, &end
, FALSE
);
9547 if (chars
&& strlen(chars
) > 0)
9548 compose
->modified
= TRUE
;
9550 } else if (buf
[0] == '1') { /* failed */
9551 g_warning("Couldn't exec external editor");
9552 if (claws_unlink(compose
->exteditor_file
) < 0)
9553 FILE_OP_ERROR(compose
->exteditor_file
, "unlink");
9554 } else if (buf
[0] == '2') {
9555 g_warning("Couldn't write to file");
9556 } else if (buf
[0] == '3') {
9557 g_warning("Pipe read failed");
9560 compose_set_ext_editor_sensitive(compose
, TRUE
);
9562 g_free(compose
->exteditor_file
);
9563 compose
->exteditor_file
= NULL
;
9564 compose
->exteditor_pid
= -1;
9565 compose
->exteditor_ch
= NULL
;
9566 compose
->exteditor_tag
= -1;
9567 if (compose
->exteditor_socket
) {
9568 gtk_widget_destroy(compose
->exteditor_socket
);
9569 compose
->exteditor_socket
= NULL
;
9576 static char *ext_editor_menu_entries
[] = {
9577 "Menu/Message/Send",
9578 "Menu/Message/SendLater",
9579 "Menu/Message/InsertFile",
9580 "Menu/Message/InsertSig",
9581 "Menu/Message/ReplaceSig",
9582 "Menu/Message/Save",
9583 "Menu/Message/Print",
9588 "Menu/Tools/ShowRuler",
9589 "Menu/Tools/Actions",
9594 static void compose_set_ext_editor_sensitive(Compose
*compose
,
9599 for (i
= 0; ext_editor_menu_entries
[i
]; ++i
) {
9600 cm_menu_set_sensitive_full(compose
->ui_manager
,
9601 ext_editor_menu_entries
[i
], sensitive
);
9604 if (compose_get_ext_editor_uses_socket()) {
9606 if (compose
->exteditor_socket
)
9607 gtk_widget_hide(compose
->exteditor_socket
);
9608 gtk_widget_show(compose
->scrolledwin
);
9609 if (prefs_common
.show_ruler
)
9610 gtk_widget_show(compose
->ruler_hbox
);
9611 /* Fix the focus, as it doesn't go anywhere when the
9612 * socket is hidden or destroyed */
9613 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9615 g_assert (compose
->exteditor_socket
!= NULL
);
9616 /* Fix the focus, as it doesn't go anywhere when the
9617 * edit box is hidden */
9618 if (gtk_widget_is_focus(compose
->text
))
9619 gtk_widget_child_focus(compose
->window
, GTK_DIR_TAB_BACKWARD
);
9620 gtk_widget_hide(compose
->scrolledwin
);
9621 gtk_widget_hide(compose
->ruler_hbox
);
9622 gtk_widget_show(compose
->exteditor_socket
);
9625 gtk_widget_set_sensitive(compose
->text
, sensitive
);
9627 if (compose
->toolbar
->send_btn
)
9628 gtk_widget_set_sensitive(compose
->toolbar
->send_btn
, sensitive
);
9629 if (compose
->toolbar
->sendl_btn
)
9630 gtk_widget_set_sensitive(compose
->toolbar
->sendl_btn
, sensitive
);
9631 if (compose
->toolbar
->draft_btn
)
9632 gtk_widget_set_sensitive(compose
->toolbar
->draft_btn
, sensitive
);
9633 if (compose
->toolbar
->insert_btn
)
9634 gtk_widget_set_sensitive(compose
->toolbar
->insert_btn
, sensitive
);
9635 if (compose
->toolbar
->sig_btn
)
9636 gtk_widget_set_sensitive(compose
->toolbar
->sig_btn
, sensitive
);
9637 if (compose
->toolbar
->exteditor_btn
)
9638 gtk_widget_set_sensitive(compose
->toolbar
->exteditor_btn
, sensitive
);
9639 if (compose
->toolbar
->linewrap_current_btn
)
9640 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_current_btn
, sensitive
);
9641 if (compose
->toolbar
->linewrap_all_btn
)
9642 gtk_widget_set_sensitive(compose
->toolbar
->linewrap_all_btn
, sensitive
);
9645 static gboolean
compose_get_ext_editor_uses_socket()
9647 return (prefs_common_get_ext_editor_cmd() &&
9648 strstr(prefs_common_get_ext_editor_cmd(), "%w"));
9651 static gboolean
compose_ext_editor_plug_removed_cb(GtkSocket
*socket
, Compose
*compose
)
9653 compose
->exteditor_socket
= NULL
;
9654 /* returning FALSE allows destruction of the socket */
9657 #endif /* G_OS_UNIX */
9660 * compose_undo_state_changed:
9662 * Change the sensivity of the menuentries undo and redo
9664 static void compose_undo_state_changed(UndoMain
*undostruct
, gint undo_state
,
9665 gint redo_state
, gpointer data
)
9667 Compose
*compose
= (Compose
*)data
;
9669 switch (undo_state
) {
9670 case UNDO_STATE_TRUE
:
9671 if (!undostruct
->undo_state
) {
9672 undostruct
->undo_state
= TRUE
;
9673 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", TRUE
);
9676 case UNDO_STATE_FALSE
:
9677 if (undostruct
->undo_state
) {
9678 undostruct
->undo_state
= FALSE
;
9679 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", FALSE
);
9682 case UNDO_STATE_UNCHANGED
:
9684 case UNDO_STATE_REFRESH
:
9685 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Undo", undostruct
->undo_state
);
9688 g_warning("Undo state not recognized");
9692 switch (redo_state
) {
9693 case UNDO_STATE_TRUE
:
9694 if (!undostruct
->redo_state
) {
9695 undostruct
->redo_state
= TRUE
;
9696 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", TRUE
);
9699 case UNDO_STATE_FALSE
:
9700 if (undostruct
->redo_state
) {
9701 undostruct
->redo_state
= FALSE
;
9702 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", FALSE
);
9705 case UNDO_STATE_UNCHANGED
:
9707 case UNDO_STATE_REFRESH
:
9708 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit/Redo", undostruct
->redo_state
);
9711 g_warning("Redo state not recognized");
9716 /* callback functions */
9718 static void compose_notebook_size_alloc(GtkNotebook
*notebook
,
9719 GtkAllocation
*allocation
,
9722 prefs_common
.compose_notebook_height
= gtk_paned_get_position(paned
);
9725 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9726 * includes "non-client" (windows-izm) in calculation, so this calculation
9727 * may not be accurate.
9729 static gboolean
compose_edit_size_alloc(GtkEditable
*widget
,
9730 GtkAllocation
*allocation
,
9731 GtkSHRuler
*shruler
)
9733 if (prefs_common
.show_ruler
) {
9734 gint char_width
= 0, char_height
= 0;
9735 gint line_width_in_chars
;
9737 gtkut_get_font_size(GTK_WIDGET(widget
),
9738 &char_width
, &char_height
);
9739 line_width_in_chars
=
9740 (allocation
->width
- allocation
->x
) / char_width
;
9742 /* got the maximum */
9743 gtk_shruler_set_range(GTK_SHRULER(shruler
),
9744 0.0, line_width_in_chars
, 0);
9753 ComposePrefType type
;
9754 gboolean entry_marked
;
9757 static void account_activated(GtkComboBox
*optmenu
, gpointer data
)
9759 Compose
*compose
= (Compose
*)data
;
9762 gchar
*folderidentifier
;
9763 gint account_id
= 0;
9766 GSList
*list
, *saved_list
= NULL
;
9767 HeaderEntryState
*state
;
9768 GtkRcStyle
*style
= NULL
;
9769 #if !GTK_CHECK_VERSION(3, 0, 0)
9770 static GdkColor yellow
;
9771 static gboolean color_set
= FALSE
;
9773 static GdkColor yellow
= { (guint32
)0, (guint32
)0xf5, (guint32
)0xf6, (guint32
)0xbe };
9776 /* Get ID of active account in the combo box */
9777 menu
= gtk_combo_box_get_model(optmenu
);
9778 cm_return_if_fail(gtk_combo_box_get_active_iter(optmenu
, &iter
));
9779 gtk_tree_model_get(menu
, &iter
, 1, &account_id
, -1);
9781 ac
= account_find_from_id(account_id
);
9782 cm_return_if_fail(ac
!= NULL
);
9784 if (ac
!= compose
->account
) {
9785 compose_select_account(compose
, ac
, FALSE
);
9787 for (list
= compose
->header_list
; list
; list
= list
->next
) {
9788 ComposeHeaderEntry
*hentry
=(ComposeHeaderEntry
*)list
->data
;
9790 if (hentry
->type
== PREF_ACCOUNT
|| !list
->next
) {
9791 compose_destroy_headerentry(compose
, hentry
);
9795 state
= g_malloc0(sizeof(HeaderEntryState
));
9796 state
->header
= gtk_editable_get_chars(GTK_EDITABLE(
9797 gtk_bin_get_child(GTK_BIN(hentry
->combo
))), 0, -1);
9798 state
->entry
= gtk_editable_get_chars(
9799 GTK_EDITABLE(hentry
->entry
), 0, -1);
9800 state
->type
= hentry
->type
;
9802 #if !GTK_CHECK_VERSION(3, 0, 0)
9804 gdk_color_parse("#f5f6be", &yellow
);
9805 color_set
= gdk_colormap_alloc_color(
9806 gdk_colormap_get_system(),
9807 &yellow
, FALSE
, TRUE
);
9811 style
= gtk_widget_get_modifier_style(hentry
->entry
);
9812 state
->entry_marked
= gdk_color_equal(&yellow
,
9813 &style
->base
[GTK_STATE_NORMAL
]);
9815 saved_list
= g_slist_append(saved_list
, state
);
9816 compose_destroy_headerentry(compose
, hentry
);
9819 compose
->header_last
= NULL
;
9820 g_slist_free(compose
->header_list
);
9821 compose
->header_list
= NULL
;
9822 compose
->header_nextrow
= 1;
9823 compose_create_header_entry(compose
);
9825 if (ac
->set_autocc
&& ac
->auto_cc
)
9826 compose_entry_append(compose
, ac
->auto_cc
,
9827 COMPOSE_CC
, PREF_ACCOUNT
);
9829 if (ac
->set_autobcc
&& ac
->auto_bcc
)
9830 compose_entry_append(compose
, ac
->auto_bcc
,
9831 COMPOSE_BCC
, PREF_ACCOUNT
);
9833 if (ac
->set_autoreplyto
&& ac
->auto_replyto
)
9834 compose_entry_append(compose
, ac
->auto_replyto
,
9835 COMPOSE_REPLYTO
, PREF_ACCOUNT
);
9837 for (list
= saved_list
; list
; list
= list
->next
) {
9838 state
= (HeaderEntryState
*) list
->data
;
9840 compose_add_header_entry(compose
, state
->header
,
9841 state
->entry
, state
->type
);
9842 if (state
->entry_marked
)
9843 compose_entry_mark_default_to(compose
, state
->entry
);
9845 g_free(state
->header
);
9846 g_free(state
->entry
);
9849 g_slist_free(saved_list
);
9851 combobox_select_by_data(GTK_COMBO_BOX(compose
->header_last
->combo
),
9852 (ac
->protocol
== A_NNTP
) ?
9853 COMPOSE_NEWSGROUPS
: COMPOSE_TO
);
9856 /* Set message save folder */
9857 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
9858 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
), prefs_common
.savemsg
);
9860 g_signal_connect(G_OBJECT(compose
->savemsg_checkbtn
), "toggled",
9861 G_CALLBACK(compose_savemsg_checkbtn_cb
), compose
);
9863 compose_set_save_to(compose
, NULL
);
9864 if (account_get_special_folder(compose
->account
, F_OUTBOX
)) {
9865 folderidentifier
= folder_item_get_identifier(account_get_special_folder
9866 (compose
->account
, F_OUTBOX
));
9867 compose_set_save_to(compose
, folderidentifier
);
9868 g_free(folderidentifier
);
9872 static void attach_selected(GtkTreeView
*tree_view
, GtkTreePath
*tree_path
,
9873 GtkTreeViewColumn
*column
, Compose
*compose
)
9875 compose_attach_property(NULL
, compose
);
9878 static gboolean
attach_button_pressed(GtkWidget
*widget
, GdkEventButton
*event
,
9881 Compose
*compose
= (Compose
*)data
;
9882 GtkTreeSelection
*attach_selection
;
9883 gint attach_nr_selected
;
9886 if (!event
) return FALSE
;
9888 if (event
->button
== 3) {
9889 attach_selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(widget
));
9890 attach_nr_selected
= gtk_tree_selection_count_selected_rows(attach_selection
);
9892 /* If no rows, or just one row is selected, right-click should
9893 * open menu relevant to the row being right-clicked on. We
9894 * achieve that by selecting the clicked row first. If more
9895 * than one row is selected, we shouldn't modify the selection,
9896 * as user may want to remove selected rows (attachments). */
9897 if (attach_nr_selected
< 2) {
9898 gtk_tree_selection_unselect_all(attach_selection
);
9899 attach_nr_selected
= 0;
9900 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget
),
9901 event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
9903 gtk_tree_selection_select_path(attach_selection
, path
);
9904 gtk_tree_path_free(path
);
9905 attach_nr_selected
++;
9909 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Remove", (attach_nr_selected
> 0));
9910 /* Properties menu item makes no sense with more than one row
9911 * selected, the properties dialog can only edit one attachment. */
9912 cm_menu_set_sensitive_full(compose
->ui_manager
, "Popup/Compose/Properties", (attach_nr_selected
== 1));
9914 gtk_menu_popup(GTK_MENU(compose
->popupmenu
), NULL
, NULL
,
9915 NULL
, NULL
, event
->button
, event
->time
);
9922 static gboolean
attach_key_pressed(GtkWidget
*widget
, GdkEventKey
*event
,
9925 Compose
*compose
= (Compose
*)data
;
9927 if (!event
) return FALSE
;
9929 switch (event
->keyval
) {
9930 case GDK_KEY_Delete
:
9931 compose_attach_remove_selected(NULL
, compose
);
9937 static void compose_allow_user_actions (Compose
*compose
, gboolean allow
)
9939 toolbar_comp_set_sensitive(compose
, allow
);
9940 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Message", allow
);
9941 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Edit", allow
);
9943 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", allow
);
9945 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Options", allow
);
9946 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Tools", allow
);
9947 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Help", allow
);
9949 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose
->text
), allow
);
9953 static void compose_send_cb(GtkAction
*action
, gpointer data
)
9955 Compose
*compose
= (Compose
*)data
;
9958 if (compose
->exteditor_tag
!= -1) {
9959 debug_print("ignoring send: external editor still open\n");
9963 if (prefs_common
.work_offline
&&
9964 !inc_offline_should_override(TRUE
,
9965 _("Claws Mail needs network access in order "
9966 "to send this email.")))
9969 if (compose
->draft_timeout_tag
>= 0) { /* CLAWS: disable draft timeout */
9970 g_source_remove(compose
->draft_timeout_tag
);
9971 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
9974 compose_send(compose
);
9977 static void compose_send_later_cb(GtkAction
*action
, gpointer data
)
9979 Compose
*compose
= (Compose
*)data
;
9983 compose_allow_user_actions(compose
, FALSE
);
9984 val
= compose_queue_sub(compose
, NULL
, NULL
, NULL
, TRUE
, TRUE
);
9985 compose_allow_user_actions(compose
, TRUE
);
9989 compose_close(compose
);
9990 } else if (val
== -1) {
9991 alertpanel_error(_("Could not queue message."));
9992 } else if (val
== -2) {
9993 alertpanel_error(_("Could not queue message:\n\n%s."), g_strerror(errno
));
9994 } else if (val
== -3) {
9995 if (privacy_peek_error())
9996 alertpanel_error(_("Could not queue message for sending:\n\n"
9997 "Signature failed: %s"), privacy_get_error());
9998 } else if (val
== -4) {
9999 alertpanel_error(_("Could not queue message for sending:\n\n"
10000 "Charset conversion failed."));
10001 } else if (val
== -5) {
10002 alertpanel_error(_("Could not queue message for sending:\n\n"
10003 "Couldn't get recipient encryption key."));
10004 } else if (val
== -6) {
10007 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
10010 #define DRAFTED_AT_EXIT "drafted_at_exit"
10011 static void compose_register_draft(MsgInfo
*info
)
10013 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10014 DRAFTED_AT_EXIT
, NULL
);
10015 FILE *fp
= g_fopen(filepath
, "ab");
10018 fprintf(fp
, "%s\t%d\n", folder_item_get_identifier(info
->folder
),
10026 gboolean
compose_draft (gpointer data
, guint action
)
10028 Compose
*compose
= (Compose
*)data
;
10033 MsgFlags flag
= {0, 0};
10034 static gboolean lock
= FALSE
;
10035 MsgInfo
*newmsginfo
;
10037 gboolean target_locked
= FALSE
;
10038 gboolean err
= FALSE
;
10040 if (lock
) return FALSE
;
10042 if (compose
->sending
)
10045 draft
= account_get_special_folder(compose
->account
, F_DRAFT
);
10046 cm_return_val_if_fail(draft
!= NULL
, FALSE
);
10048 if (!g_mutex_trylock(compose
->mutex
)) {
10049 /* we don't want to lock the mutex once it's available,
10050 * because as the only other part of compose.c locking
10051 * it is compose_close - which means once unlocked,
10052 * the compose struct will be freed */
10053 debug_print("couldn't lock mutex, probably sending\n");
10059 tmp
= g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
10060 G_DIR_SEPARATOR
, compose
);
10061 if ((fp
= g_fopen(tmp
, "wb")) == NULL
) {
10062 FILE_OP_ERROR(tmp
, "fopen");
10066 /* chmod for security */
10067 if (change_file_mode_rw(fp
, tmp
) < 0) {
10068 FILE_OP_ERROR(tmp
, "chmod");
10069 g_warning("can't change file mode");
10072 /* Save draft infos */
10073 err
|= (fprintf(fp
, "X-Claws-Account-Id:%d\n", compose
->account
->account_id
) < 0);
10074 err
|= (fprintf(fp
, "S:%s\n", compose
->account
->address
) < 0);
10076 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose
->savemsg_checkbtn
))) {
10077 gchar
*savefolderid
;
10079 savefolderid
= compose_get_save_to(compose
);
10080 err
|= (fprintf(fp
, "SCF:%s\n", savefolderid
) < 0);
10081 g_free(savefolderid
);
10083 if (compose
->return_receipt
) {
10084 err
|= (fprintf(fp
, "RRCPT:1\n") < 0);
10086 if (compose
->privacy_system
) {
10087 err
|= (fprintf(fp
, "X-Claws-Sign:%d\n", compose
->use_signing
) < 0);
10088 err
|= (fprintf(fp
, "X-Claws-Encrypt:%d\n", compose
->use_encryption
) < 0);
10089 err
|= (fprintf(fp
, "X-Claws-Privacy-System:%s\n", compose
->privacy_system
) < 0);
10092 /* Message-ID of message replying to */
10093 if ((compose
->replyinfo
!= NULL
) && (compose
->replyinfo
->msgid
!= NULL
)) {
10094 gchar
*folderid
= NULL
;
10096 if (compose
->replyinfo
->folder
)
10097 folderid
= folder_item_get_identifier(compose
->replyinfo
->folder
);
10098 if (folderid
== NULL
)
10099 folderid
= g_strdup("NULL");
10101 err
|= (fprintf(fp
, "RMID:%s\t%d\t%s\n", folderid
, compose
->replyinfo
->msgnum
, compose
->replyinfo
->msgid
) < 0);
10104 /* Message-ID of message forwarding to */
10105 if ((compose
->fwdinfo
!= NULL
) && (compose
->fwdinfo
->msgid
!= NULL
)) {
10106 gchar
*folderid
= NULL
;
10108 if (compose
->fwdinfo
->folder
)
10109 folderid
= folder_item_get_identifier(compose
->fwdinfo
->folder
);
10110 if (folderid
== NULL
)
10111 folderid
= g_strdup("NULL");
10113 err
|= (fprintf(fp
, "FMID:%s\t%d\t%s\n", folderid
, compose
->fwdinfo
->msgnum
, compose
->fwdinfo
->msgid
) < 0);
10117 err
|= (fprintf(fp
, "X-Claws-Auto-Wrapping:%d\n", compose
->autowrap
) < 0);
10118 err
|= (fprintf(fp
, "X-Claws-Auto-Indent:%d\n", compose
->autoindent
) < 0);
10120 sheaders
= compose_get_manual_headers_info(compose
);
10121 err
|= (fprintf(fp
, "X-Claws-Manual-Headers:%s\n", sheaders
) < 0);
10124 /* end of headers */
10125 err
|= (fprintf(fp
, "X-Claws-End-Special-Headers: 1\n") < 0);
10132 if (compose_write_to_file(compose
, fp
, COMPOSE_WRITE_FOR_STORE
, action
!= COMPOSE_AUTO_SAVE
) < 0) {
10136 if (fclose(fp
) == EOF
) {
10140 flag
.perm_flags
= MSG_NEW
|MSG_UNREAD
;
10141 if (compose
->targetinfo
) {
10142 target_locked
= MSG_IS_LOCKED(compose
->targetinfo
->flags
);
10144 flag
.perm_flags
|= MSG_LOCKED
;
10146 flag
.tmp_flags
= MSG_DRAFT
;
10148 folder_item_scan(draft
);
10149 if ((msgnum
= folder_item_add_msg(draft
, tmp
, &flag
, TRUE
)) < 0) {
10150 MsgInfo
*tmpinfo
= NULL
;
10151 debug_print("didn't get msgnum after adding draft [%s]\n", compose
->msgid
?compose
->msgid
:"no msgid");
10152 if (compose
->msgid
) {
10153 tmpinfo
= folder_item_get_msginfo_by_msgid(draft
, compose
->msgid
);
10156 msgnum
= tmpinfo
->msgnum
;
10157 procmsg_msginfo_free(&tmpinfo
);
10158 debug_print("got draft msgnum %d from scanning\n", msgnum
);
10160 debug_print("didn't get draft msgnum after scanning\n");
10163 debug_print("got draft msgnum %d from adding\n", msgnum
);
10169 if (action
!= COMPOSE_AUTO_SAVE
) {
10170 if (action
!= COMPOSE_DRAFT_FOR_EXIT
)
10171 alertpanel_error(_("Could not save draft."));
10174 gtkut_window_popup(compose
->window
);
10175 val
= alertpanel_full(_("Could not save draft"),
10176 _("Could not save draft.\n"
10177 "Do you want to cancel exit or discard this email?"),
10178 _("_Cancel exit"), _("_Discard email"), NULL
,
10179 FALSE
, NULL
, ALERT_QUESTION
, G_ALERTDEFAULT
);
10180 if (val
== G_ALERTALTERNATE
) {
10182 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
10183 compose_close(compose
);
10187 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
10196 if (compose
->mode
== COMPOSE_REEDIT
) {
10197 compose_remove_reedit_target(compose
, TRUE
);
10200 newmsginfo
= folder_item_get_msginfo(draft
, msgnum
);
10203 procmsg_msginfo_unset_flags(newmsginfo
, ~0, ~0);
10205 procmsg_msginfo_set_flags(newmsginfo
, MSG_NEW
|MSG_UNREAD
|MSG_LOCKED
, MSG_DRAFT
);
10207 procmsg_msginfo_set_flags(newmsginfo
, MSG_NEW
|MSG_UNREAD
, MSG_DRAFT
);
10208 if (compose_use_attach(compose
) && action
!= COMPOSE_AUTO_SAVE
)
10209 procmsg_msginfo_set_flags(newmsginfo
, 0,
10210 MSG_HAS_ATTACHMENT
);
10212 if (action
== COMPOSE_DRAFT_FOR_EXIT
) {
10213 compose_register_draft(newmsginfo
);
10215 procmsg_msginfo_free(&newmsginfo
);
10218 folder_item_scan(draft
);
10220 if (action
== COMPOSE_QUIT_EDITING
|| action
== COMPOSE_DRAFT_FOR_EXIT
) {
10222 g_mutex_unlock(compose
->mutex
); /* must be done before closing */
10223 compose_close(compose
);
10229 path
= folder_item_fetch_msg(draft
, msgnum
);
10230 if (path
== NULL
) {
10231 debug_print("can't fetch %s:%d\n", draft
->path
, msgnum
);
10234 if (g_stat(path
, &s
) < 0) {
10235 FILE_OP_ERROR(path
, "stat");
10241 procmsg_msginfo_free(&(compose
->targetinfo
));
10242 compose
->targetinfo
= procmsg_msginfo_new();
10243 compose
->targetinfo
->msgnum
= msgnum
;
10244 compose
->targetinfo
->size
= (goffset
)s
.st_size
;
10245 compose
->targetinfo
->mtime
= s
.st_mtime
;
10246 compose
->targetinfo
->folder
= draft
;
10248 procmsg_msginfo_set_flags(compose
->targetinfo
, MSG_LOCKED
, 0);
10249 compose
->mode
= COMPOSE_REEDIT
;
10251 if (action
== COMPOSE_AUTO_SAVE
) {
10252 compose
->autosaved_draft
= compose
->targetinfo
;
10254 compose
->modified
= FALSE
;
10255 compose_set_title(compose
);
10259 g_mutex_unlock(compose
->mutex
);
10263 void compose_clear_exit_drafts(void)
10265 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10266 DRAFTED_AT_EXIT
, NULL
);
10267 if (is_file_exist(filepath
))
10268 claws_unlink(filepath
);
10273 void compose_reopen_exit_drafts(void)
10275 gchar
*filepath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
10276 DRAFTED_AT_EXIT
, NULL
);
10277 FILE *fp
= g_fopen(filepath
, "rb");
10281 while (fgets(buf
, sizeof(buf
), fp
)) {
10282 gchar
**parts
= g_strsplit(buf
, "\t", 2);
10283 const gchar
*folder
= parts
[0];
10284 int msgnum
= parts
[1] ? atoi(parts
[1]):-1;
10286 if (folder
&& *folder
&& msgnum
> -1) {
10287 FolderItem
*item
= folder_find_item_from_identifier(folder
);
10288 MsgInfo
*info
= folder_item_get_msginfo(item
, msgnum
);
10290 compose_reedit(info
, FALSE
);
10297 compose_clear_exit_drafts();
10300 static void compose_save_cb(GtkAction
*action
, gpointer data
)
10302 Compose
*compose
= (Compose
*)data
;
10303 compose_draft(compose
, COMPOSE_KEEP_EDITING
);
10304 compose
->rmode
= COMPOSE_REEDIT
;
10307 void compose_attach_from_list(Compose
*compose
, GList
*file_list
, gboolean free_data
)
10309 if (compose
&& file_list
) {
10312 for ( tmp
= file_list
; tmp
; tmp
= tmp
->next
) {
10313 gchar
*file
= (gchar
*) tmp
->data
;
10314 gchar
*utf8_filename
= conv_filename_to_utf8(file
);
10315 compose_attach_append(compose
, file
, utf8_filename
, NULL
, NULL
);
10316 compose_changed_cb(NULL
, compose
);
10321 g_free(utf8_filename
);
10326 static void compose_attach_cb(GtkAction
*action
, gpointer data
)
10328 Compose
*compose
= (Compose
*)data
;
10331 if (compose
->redirect_filename
!= NULL
)
10334 /* Set focus_window properly, in case we were called via popup menu,
10335 * which unsets it (via focus_out_event callback on compose window). */
10336 manage_window_focus_in(compose
->window
, NULL
, NULL
);
10338 file_list
= filesel_select_multiple_files_open(_("Select file"), NULL
);
10341 compose_attach_from_list(compose
, file_list
, TRUE
);
10342 g_list_free(file_list
);
10346 static void compose_insert_file_cb(GtkAction
*action
, gpointer data
)
10348 Compose
*compose
= (Compose
*)data
;
10350 gint files_inserted
= 0;
10352 file_list
= filesel_select_multiple_files_open(_("Select file"), NULL
);
10357 for ( tmp
= file_list
; tmp
; tmp
= tmp
->next
) {
10358 gchar
*file
= (gchar
*) tmp
->data
;
10359 gchar
*filedup
= g_strdup(file
);
10360 gchar
*shortfile
= g_path_get_basename(filedup
);
10361 ComposeInsertResult res
;
10362 /* insert the file if the file is short or if the user confirmed that
10363 he/she wants to insert the large file */
10364 res
= compose_insert_file(compose
, file
);
10365 if (res
== COMPOSE_INSERT_READ_ERROR
) {
10366 alertpanel_error(_("File '%s' could not be read."), shortfile
);
10367 } else if (res
== COMPOSE_INSERT_INVALID_CHARACTER
) {
10368 alertpanel_error(_("File '%s' contained invalid characters\n"
10369 "for the current encoding, insertion may be incorrect."),
10371 } else if (res
== COMPOSE_INSERT_SUCCESS
)
10378 g_list_free(file_list
);
10382 if (files_inserted
> 0 && compose
->gtkaspell
&&
10383 compose
->gtkaspell
->check_while_typing
)
10384 gtkaspell_highlight_all(compose
->gtkaspell
);
10388 static void compose_insert_sig_cb(GtkAction
*action
, gpointer data
)
10390 Compose
*compose
= (Compose
*)data
;
10392 compose_insert_sig(compose
, FALSE
);
10395 static void compose_replace_sig_cb(GtkAction
*action
, gpointer data
)
10397 Compose
*compose
= (Compose
*)data
;
10399 compose_insert_sig(compose
, TRUE
);
10402 static gint
compose_delete_cb(GtkWidget
*widget
, GdkEventAny
*event
,
10406 Compose
*compose
= (Compose
*)data
;
10408 gtkut_widget_get_uposition(widget
, &x
, &y
);
10409 if (!compose
->batch
) {
10410 prefs_common
.compose_x
= x
;
10411 prefs_common
.compose_y
= y
;
10413 if (compose
->sending
|| compose
->updating
)
10415 compose_close_cb(NULL
, compose
);
10419 void compose_close_toolbar(Compose
*compose
)
10421 compose_close_cb(NULL
, compose
);
10424 static gboolean
compose_can_autosave(Compose
*compose
)
10426 if (compose
->privacy_system
&& compose
->use_encryption
)
10427 return prefs_common
.autosave
&& prefs_common
.autosave_encrypted
;
10429 return prefs_common
.autosave
;
10432 static void compose_close_cb(GtkAction
*action
, gpointer data
)
10434 Compose
*compose
= (Compose
*)data
;
10438 if (compose
->exteditor_tag
!= -1) {
10439 if (!compose_ext_editor_kill(compose
))
10444 if (compose
->modified
) {
10445 gboolean reedit
= (compose
->rmode
== COMPOSE_REEDIT
);
10446 if (!g_mutex_trylock(compose
->mutex
)) {
10447 /* we don't want to lock the mutex once it's available,
10448 * because as the only other part of compose.c locking
10449 * it is compose_close - which means once unlocked,
10450 * the compose struct will be freed */
10451 debug_print("couldn't lock mutex, probably sending\n");
10455 val
= alertpanel(_("Discard message"),
10456 _("This message has been modified. Discard it?"),
10457 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL
);
10459 val
= alertpanel(_("Save changes"),
10460 _("This message has been modified. Save the latest changes?"),
10461 _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL
),
10464 g_mutex_unlock(compose
->mutex
);
10466 case G_ALERTDEFAULT
:
10467 if (compose_can_autosave(compose
) && !reedit
)
10468 compose_remove_draft(compose
);
10470 case G_ALERTALTERNATE
:
10471 compose_draft(data
, COMPOSE_QUIT_EDITING
);
10478 compose_close(compose
);
10481 static void compose_print_cb(GtkAction
*action
, gpointer data
)
10483 Compose
*compose
= (Compose
*) data
;
10485 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
10486 if (compose
->targetinfo
)
10487 messageview_print(compose
->targetinfo
, FALSE
, -1, -1, 0);
10490 static void compose_set_encoding_cb(GtkAction
*action
, GtkRadioAction
*current
, gpointer data
)
10492 gboolean active
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current
));
10493 gint value
= gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current
));
10494 Compose
*compose
= (Compose
*) data
;
10497 compose
->out_encoding
= (CharSet
)value
;
10500 static void compose_address_cb(GtkAction
*action
, gpointer data
)
10502 Compose
*compose
= (Compose
*)data
;
10504 #ifndef USE_ALT_ADDRBOOK
10505 addressbook_open(compose
);
10507 GError
* error
= NULL
;
10508 addressbook_connect_signals(compose
);
10509 addressbook_dbus_open(TRUE
, &error
);
10511 g_warning("%s", error
->message
);
10512 g_error_free(error
);
10517 static void about_show_cb(GtkAction
*action
, gpointer data
)
10522 static void compose_template_activate_cb(GtkWidget
*widget
, gpointer data
)
10524 Compose
*compose
= (Compose
*)data
;
10529 tmpl
= g_object_get_data(G_OBJECT(widget
), "template");
10530 cm_return_if_fail(tmpl
!= NULL
);
10532 msg
= g_strdup_printf(_("Do you want to apply the template '%s'?"),
10534 val
= alertpanel(_("Apply template"), msg
,
10535 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL
);
10538 if (val
== G_ALERTDEFAULT
)
10539 compose_template_apply(compose
, tmpl
, TRUE
);
10540 else if (val
== G_ALERTALTERNATE
)
10541 compose_template_apply(compose
, tmpl
, FALSE
);
10544 static void compose_ext_editor_cb(GtkAction
*action
, gpointer data
)
10546 Compose
*compose
= (Compose
*)data
;
10549 if (compose
->exteditor_tag
!= -1) {
10550 debug_print("ignoring open external editor: external editor still open\n");
10554 compose_exec_ext_editor(compose
);
10557 static void compose_undo_cb(GtkAction
*action
, gpointer data
)
10559 Compose
*compose
= (Compose
*)data
;
10560 gboolean prev_autowrap
= compose
->autowrap
;
10562 compose
->autowrap
= FALSE
;
10563 undo_undo(compose
->undostruct
);
10564 compose
->autowrap
= prev_autowrap
;
10567 static void compose_redo_cb(GtkAction
*action
, gpointer data
)
10569 Compose
*compose
= (Compose
*)data
;
10570 gboolean prev_autowrap
= compose
->autowrap
;
10572 compose
->autowrap
= FALSE
;
10573 undo_redo(compose
->undostruct
);
10574 compose
->autowrap
= prev_autowrap
;
10577 static void entry_cut_clipboard(GtkWidget
*entry
)
10579 if (GTK_IS_EDITABLE(entry
))
10580 gtk_editable_cut_clipboard (GTK_EDITABLE(entry
));
10581 else if (GTK_IS_TEXT_VIEW(entry
))
10582 gtk_text_buffer_cut_clipboard(
10583 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
)),
10584 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
),
10588 static void entry_copy_clipboard(GtkWidget
*entry
)
10590 if (GTK_IS_EDITABLE(entry
))
10591 gtk_editable_copy_clipboard (GTK_EDITABLE(entry
));
10592 else if (GTK_IS_TEXT_VIEW(entry
))
10593 gtk_text_buffer_copy_clipboard(
10594 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
)),
10595 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD
));
10598 static void entry_paste_clipboard(Compose
*compose
, GtkWidget
*entry
,
10599 gboolean wrap
, GdkAtom clip
, GtkTextIter
*insert_place
)
10601 if (GTK_IS_TEXT_VIEW(entry
)) {
10602 GtkTextBuffer
*buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
));
10603 GtkTextMark
*mark_start
= gtk_text_buffer_get_insert(buffer
);
10604 GtkTextIter start_iter
, end_iter
;
10606 gchar
*contents
= gtk_clipboard_wait_for_text(gtk_clipboard_get(clip
));
10608 if (contents
== NULL
)
10611 /* we shouldn't delete the selection when middle-click-pasting, or we
10612 * can't mid-click-paste our own selection */
10613 if (clip
!= GDK_SELECTION_PRIMARY
) {
10614 undo_paste_clipboard(GTK_TEXT_VIEW(compose
->text
), compose
->undostruct
);
10615 gtk_text_buffer_delete_selection(buffer
, FALSE
, TRUE
);
10618 if (insert_place
== NULL
) {
10619 /* if insert_place isn't specified, insert at the cursor.
10620 * used for Ctrl-V pasting */
10621 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark_start
);
10622 start
= gtk_text_iter_get_offset(&start_iter
);
10623 gtk_text_buffer_insert(buffer
, &start_iter
, contents
, strlen(contents
));
10625 /* if insert_place is specified, paste here.
10626 * used for mid-click-pasting */
10627 start
= gtk_text_iter_get_offset(insert_place
);
10628 gtk_text_buffer_insert(buffer
, insert_place
, contents
, strlen(contents
));
10629 if (prefs_common
.primary_paste_unselects
)
10630 gtk_text_buffer_select_range(buffer
, insert_place
, insert_place
);
10634 /* paste unwrapped: mark the paste so it's not wrapped later */
10635 end
= start
+ strlen(contents
);
10636 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, start
);
10637 gtk_text_buffer_get_iter_at_offset(buffer
, &end_iter
, end
);
10638 gtk_text_buffer_apply_tag_by_name(buffer
, "no_wrap", &start_iter
, &end_iter
);
10639 } else if (wrap
&& clip
== GDK_SELECTION_PRIMARY
) {
10640 /* rewrap paragraph now (after a mid-click-paste) */
10641 mark_start
= gtk_text_buffer_get_insert(buffer
);
10642 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark_start
);
10643 gtk_text_iter_backward_char(&start_iter
);
10644 compose_beautify_paragraph(compose
, &start_iter
, TRUE
);
10646 } else if (GTK_IS_EDITABLE(entry
))
10647 gtk_editable_paste_clipboard (GTK_EDITABLE(entry
));
10649 compose
->modified
= TRUE
;
10652 static void entry_allsel(GtkWidget
*entry
)
10654 if (GTK_IS_EDITABLE(entry
))
10655 gtk_editable_select_region(GTK_EDITABLE(entry
), 0, -1);
10656 else if (GTK_IS_TEXT_VIEW(entry
)) {
10657 GtkTextIter startiter
, enditer
;
10658 GtkTextBuffer
*textbuf
;
10660 textbuf
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry
));
10661 gtk_text_buffer_get_start_iter(textbuf
, &startiter
);
10662 gtk_text_buffer_get_end_iter(textbuf
, &enditer
);
10664 gtk_text_buffer_move_mark_by_name(textbuf
,
10665 "selection_bound", &startiter
);
10666 gtk_text_buffer_move_mark_by_name(textbuf
,
10667 "insert", &enditer
);
10671 static void compose_cut_cb(GtkAction
*action
, gpointer data
)
10673 Compose
*compose
= (Compose
*)data
;
10674 if (compose
->focused_editable
10675 #ifndef GENERIC_UMPC
10676 && gtk_widget_has_focus(compose
->focused_editable
)
10679 entry_cut_clipboard(compose
->focused_editable
);
10682 static void compose_copy_cb(GtkAction
*action
, gpointer data
)
10684 Compose
*compose
= (Compose
*)data
;
10685 if (compose
->focused_editable
10686 #ifndef GENERIC_UMPC
10687 && gtk_widget_has_focus(compose
->focused_editable
)
10690 entry_copy_clipboard(compose
->focused_editable
);
10693 static void compose_paste_cb(GtkAction
*action
, gpointer data
)
10695 Compose
*compose
= (Compose
*)data
;
10696 gint prev_autowrap
;
10697 GtkTextBuffer
*buffer
;
10699 if (compose
->focused_editable
&&
10700 #ifndef GENERIC_UMPC
10701 gtk_widget_has_focus(compose
->focused_editable
)
10704 entry_paste_clipboard(compose
, compose
->focused_editable
,
10705 prefs_common
.linewrap_pastes
,
10706 GDK_SELECTION_CLIPBOARD
, NULL
);
10711 #ifndef GENERIC_UMPC
10712 gtk_widget_has_focus(compose
->text
) &&
10714 compose
->gtkaspell
&&
10715 compose
->gtkaspell
->check_while_typing
)
10716 gtkaspell_highlight_all(compose
->gtkaspell
);
10720 static void compose_paste_as_quote_cb(GtkAction
*action
, gpointer data
)
10722 Compose
*compose
= (Compose
*)data
;
10723 gint wrap_quote
= prefs_common
.linewrap_quote
;
10724 if (compose
->focused_editable
10725 #ifndef GENERIC_UMPC
10726 && gtk_widget_has_focus(compose
->focused_editable
)
10729 /* let text_insert() (called directly or at a later time
10730 * after the gtk_editable_paste_clipboard) know that
10731 * text is to be inserted as a quotation. implemented
10732 * by using a simple refcount... */
10733 gint paste_as_quotation
= GPOINTER_TO_INT(g_object_get_data(
10734 G_OBJECT(compose
->focused_editable
),
10735 "paste_as_quotation"));
10736 g_object_set_data(G_OBJECT(compose
->focused_editable
),
10737 "paste_as_quotation",
10738 GINT_TO_POINTER(paste_as_quotation
+ 1));
10739 prefs_common
.linewrap_quote
= prefs_common
.linewrap_pastes
;
10740 entry_paste_clipboard(compose
, compose
->focused_editable
,
10741 prefs_common
.linewrap_pastes
,
10742 GDK_SELECTION_CLIPBOARD
, NULL
);
10743 prefs_common
.linewrap_quote
= wrap_quote
;
10747 static void compose_paste_no_wrap_cb(GtkAction
*action
, gpointer data
)
10749 Compose
*compose
= (Compose
*)data
;
10750 gint prev_autowrap
;
10751 GtkTextBuffer
*buffer
;
10753 if (compose
->focused_editable
10754 #ifndef GENERIC_UMPC
10755 && gtk_widget_has_focus(compose
->focused_editable
)
10758 entry_paste_clipboard(compose
, compose
->focused_editable
, FALSE
,
10759 GDK_SELECTION_CLIPBOARD
, NULL
);
10764 #ifndef GENERIC_UMPC
10765 gtk_widget_has_focus(compose
->text
) &&
10767 compose
->gtkaspell
&&
10768 compose
->gtkaspell
->check_while_typing
)
10769 gtkaspell_highlight_all(compose
->gtkaspell
);
10773 static void compose_paste_wrap_cb(GtkAction
*action
, gpointer data
)
10775 Compose
*compose
= (Compose
*)data
;
10776 gint prev_autowrap
;
10777 GtkTextBuffer
*buffer
;
10779 if (compose
->focused_editable
10780 #ifndef GENERIC_UMPC
10781 && gtk_widget_has_focus(compose
->focused_editable
)
10784 entry_paste_clipboard(compose
, compose
->focused_editable
, TRUE
,
10785 GDK_SELECTION_CLIPBOARD
, NULL
);
10790 #ifndef GENERIC_UMPC
10791 gtk_widget_has_focus(compose
->text
) &&
10793 compose
->gtkaspell
&&
10794 compose
->gtkaspell
->check_while_typing
)
10795 gtkaspell_highlight_all(compose
->gtkaspell
);
10799 static void compose_allsel_cb(GtkAction
*action
, gpointer data
)
10801 Compose
*compose
= (Compose
*)data
;
10802 if (compose
->focused_editable
10803 #ifndef GENERIC_UMPC
10804 && gtk_widget_has_focus(compose
->focused_editable
)
10807 entry_allsel(compose
->focused_editable
);
10810 static void textview_move_beginning_of_line (GtkTextView
*text
)
10812 GtkTextBuffer
*buffer
;
10816 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10818 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10819 mark
= gtk_text_buffer_get_insert(buffer
);
10820 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10821 gtk_text_iter_set_line_offset(&ins
, 0);
10822 gtk_text_buffer_place_cursor(buffer
, &ins
);
10825 static void textview_move_forward_character (GtkTextView
*text
)
10827 GtkTextBuffer
*buffer
;
10831 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10833 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10834 mark
= gtk_text_buffer_get_insert(buffer
);
10835 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10836 if (gtk_text_iter_forward_cursor_position(&ins
))
10837 gtk_text_buffer_place_cursor(buffer
, &ins
);
10840 static void textview_move_backward_character (GtkTextView
*text
)
10842 GtkTextBuffer
*buffer
;
10846 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10848 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10849 mark
= gtk_text_buffer_get_insert(buffer
);
10850 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10851 if (gtk_text_iter_backward_cursor_position(&ins
))
10852 gtk_text_buffer_place_cursor(buffer
, &ins
);
10855 static void textview_move_forward_word (GtkTextView
*text
)
10857 GtkTextBuffer
*buffer
;
10862 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10864 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10865 mark
= gtk_text_buffer_get_insert(buffer
);
10866 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10867 count
= gtk_text_iter_inside_word (&ins
) ? 2 : 1;
10868 if (gtk_text_iter_forward_word_ends(&ins
, count
)) {
10869 gtk_text_iter_backward_word_start(&ins
);
10870 gtk_text_buffer_place_cursor(buffer
, &ins
);
10874 static void textview_move_backward_word (GtkTextView
*text
)
10876 GtkTextBuffer
*buffer
;
10880 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10882 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10883 mark
= gtk_text_buffer_get_insert(buffer
);
10884 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10885 if (gtk_text_iter_backward_word_starts(&ins
, 1))
10886 gtk_text_buffer_place_cursor(buffer
, &ins
);
10889 static void textview_move_end_of_line (GtkTextView
*text
)
10891 GtkTextBuffer
*buffer
;
10895 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10897 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10898 mark
= gtk_text_buffer_get_insert(buffer
);
10899 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10900 if (gtk_text_iter_forward_to_line_end(&ins
))
10901 gtk_text_buffer_place_cursor(buffer
, &ins
);
10904 static void textview_move_next_line (GtkTextView
*text
)
10906 GtkTextBuffer
*buffer
;
10911 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10913 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10914 mark
= gtk_text_buffer_get_insert(buffer
);
10915 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10916 offset
= gtk_text_iter_get_line_offset(&ins
);
10917 if (gtk_text_iter_forward_line(&ins
)) {
10918 gtk_text_iter_set_line_offset(&ins
, offset
);
10919 gtk_text_buffer_place_cursor(buffer
, &ins
);
10923 static void textview_move_previous_line (GtkTextView
*text
)
10925 GtkTextBuffer
*buffer
;
10930 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10932 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10933 mark
= gtk_text_buffer_get_insert(buffer
);
10934 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10935 offset
= gtk_text_iter_get_line_offset(&ins
);
10936 if (gtk_text_iter_backward_line(&ins
)) {
10937 gtk_text_iter_set_line_offset(&ins
, offset
);
10938 gtk_text_buffer_place_cursor(buffer
, &ins
);
10942 static void textview_delete_forward_character (GtkTextView
*text
)
10944 GtkTextBuffer
*buffer
;
10946 GtkTextIter ins
, end_iter
;
10948 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10950 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10951 mark
= gtk_text_buffer_get_insert(buffer
);
10952 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10954 if (gtk_text_iter_forward_char(&end_iter
)) {
10955 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
10959 static void textview_delete_backward_character (GtkTextView
*text
)
10961 GtkTextBuffer
*buffer
;
10963 GtkTextIter ins
, end_iter
;
10965 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10967 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10968 mark
= gtk_text_buffer_get_insert(buffer
);
10969 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10971 if (gtk_text_iter_backward_char(&end_iter
)) {
10972 gtk_text_buffer_delete(buffer
, &end_iter
, &ins
);
10976 static void textview_delete_forward_word (GtkTextView
*text
)
10978 GtkTextBuffer
*buffer
;
10980 GtkTextIter ins
, end_iter
;
10982 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
10984 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
10985 mark
= gtk_text_buffer_get_insert(buffer
);
10986 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
10988 if (gtk_text_iter_forward_word_end(&end_iter
)) {
10989 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
10993 static void textview_delete_backward_word (GtkTextView
*text
)
10995 GtkTextBuffer
*buffer
;
10997 GtkTextIter ins
, end_iter
;
10999 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11001 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11002 mark
= gtk_text_buffer_get_insert(buffer
);
11003 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11005 if (gtk_text_iter_backward_word_start(&end_iter
)) {
11006 gtk_text_buffer_delete(buffer
, &end_iter
, &ins
);
11010 static void textview_delete_line (GtkTextView
*text
)
11012 GtkTextBuffer
*buffer
;
11014 GtkTextIter ins
, start_iter
, end_iter
;
11016 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11018 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11019 mark
= gtk_text_buffer_get_insert(buffer
);
11020 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11023 gtk_text_iter_set_line_offset(&start_iter
, 0);
11026 if (gtk_text_iter_ends_line(&end_iter
)){
11027 if (!gtk_text_iter_forward_char(&end_iter
))
11028 gtk_text_iter_backward_char(&start_iter
);
11031 gtk_text_iter_forward_to_line_end(&end_iter
);
11032 gtk_text_buffer_delete(buffer
, &start_iter
, &end_iter
);
11035 static void textview_delete_to_line_end (GtkTextView
*text
)
11037 GtkTextBuffer
*buffer
;
11039 GtkTextIter ins
, end_iter
;
11041 cm_return_if_fail(GTK_IS_TEXT_VIEW(text
));
11043 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(text
));
11044 mark
= gtk_text_buffer_get_insert(buffer
);
11045 gtk_text_buffer_get_iter_at_mark(buffer
, &ins
, mark
);
11047 if (gtk_text_iter_ends_line(&end_iter
))
11048 gtk_text_iter_forward_char(&end_iter
);
11050 gtk_text_iter_forward_to_line_end(&end_iter
);
11051 gtk_text_buffer_delete(buffer
, &ins
, &end_iter
);
11054 #define DO_ACTION(name, act) { \
11055 if(!strcmp(name, a_name)) { \
11059 static ComposeCallAdvancedAction
compose_call_advanced_action_from_path(GtkAction
*action
)
11061 const gchar
*a_name
= gtk_action_get_name(action
);
11062 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER
);
11063 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER
);
11064 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD
);
11065 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD
);
11066 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
);
11067 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE
);
11068 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE
);
11069 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE
);
11070 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER
);
11071 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER
);
11072 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD
);
11073 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD
);
11074 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE
);
11075 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
);
11079 static void compose_advanced_action_cb(GtkAction
*gaction
, gpointer data
)
11081 Compose
*compose
= (Compose
*)data
;
11082 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
11083 ComposeCallAdvancedAction action
= -1;
11085 action
= compose_call_advanced_action_from_path(gaction
);
11088 void (*do_action
) (GtkTextView
*text
);
11089 } action_table
[] = {
11090 {textview_move_beginning_of_line
},
11091 {textview_move_forward_character
},
11092 {textview_move_backward_character
},
11093 {textview_move_forward_word
},
11094 {textview_move_backward_word
},
11095 {textview_move_end_of_line
},
11096 {textview_move_next_line
},
11097 {textview_move_previous_line
},
11098 {textview_delete_forward_character
},
11099 {textview_delete_backward_character
},
11100 {textview_delete_forward_word
},
11101 {textview_delete_backward_word
},
11102 {textview_delete_line
},
11103 {textview_delete_to_line_end
}
11106 if (!gtk_widget_has_focus(GTK_WIDGET(text
))) return;
11108 if (action
>= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE
&&
11109 action
<= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
) {
11110 if (action_table
[action
].do_action
)
11111 action_table
[action
].do_action(text
);
11113 g_warning("Not implemented yet.");
11117 static void compose_grab_focus_cb(GtkWidget
*widget
, Compose
*compose
)
11119 GtkAllocation allocation
;
11123 if (GTK_IS_EDITABLE(widget
)) {
11124 str
= gtk_editable_get_chars(GTK_EDITABLE(widget
), 0, -1);
11125 gtk_editable_set_position(GTK_EDITABLE(widget
),
11128 if ((parent
= gtk_widget_get_parent(widget
))
11129 && (parent
= gtk_widget_get_parent(parent
))
11130 && (parent
= gtk_widget_get_parent(parent
))) {
11131 if (GTK_IS_SCROLLED_WINDOW(parent
)) {
11132 gtk_widget_get_allocation(widget
, &allocation
);
11133 gint y
= allocation
.y
;
11134 gint height
= allocation
.height
;
11135 GtkAdjustment
*shown
= gtk_scrolled_window_get_vadjustment
11136 (GTK_SCROLLED_WINDOW(parent
));
11138 gfloat value
= gtk_adjustment_get_value(shown
);
11139 gfloat upper
= gtk_adjustment_get_upper(shown
);
11140 gfloat page_size
= gtk_adjustment_get_page_size(shown
);
11141 if (y
< (int)value
) {
11142 gtk_adjustment_set_value(shown
, y
- 1);
11144 if ((y
+ height
) > ((int)value
+ (int)page_size
)) {
11145 if ((y
- height
- 1) < ((int)upper
- (int)page_size
)) {
11146 gtk_adjustment_set_value(shown
,
11147 y
+ height
- (int)page_size
- 1);
11149 gtk_adjustment_set_value(shown
,
11150 (int)upper
- (int)page_size
- 1);
11157 if (GTK_IS_EDITABLE(widget
) || GTK_IS_TEXT_VIEW(widget
))
11158 compose
->focused_editable
= widget
;
11160 #ifdef GENERIC_UMPC
11161 if (GTK_IS_TEXT_VIEW(widget
)
11162 && gtk_paned_get_child1(GTK_PANED(compose
->paned
)) != compose
->edit_vbox
) {
11163 g_object_ref(compose
->notebook
);
11164 g_object_ref(compose
->edit_vbox
);
11165 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->notebook
);
11166 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->edit_vbox
);
11167 gtk_paned_add1(GTK_PANED(compose
->paned
), compose
->edit_vbox
);
11168 gtk_paned_add2(GTK_PANED(compose
->paned
), compose
->notebook
);
11169 g_object_unref(compose
->notebook
);
11170 g_object_unref(compose
->edit_vbox
);
11171 g_signal_handlers_block_by_func(G_OBJECT(widget
),
11172 G_CALLBACK(compose_grab_focus_cb
),
11174 gtk_widget_grab_focus(widget
);
11175 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
11176 G_CALLBACK(compose_grab_focus_cb
),
11178 } else if (!GTK_IS_TEXT_VIEW(widget
)
11179 && gtk_paned_get_child1(GTK_PANED(compose
->paned
)) != compose
->notebook
) {
11180 g_object_ref(compose
->notebook
);
11181 g_object_ref(compose
->edit_vbox
);
11182 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->notebook
);
11183 gtk_container_remove(GTK_CONTAINER(compose
->paned
), compose
->edit_vbox
);
11184 gtk_paned_add1(GTK_PANED(compose
->paned
), compose
->notebook
);
11185 gtk_paned_add2(GTK_PANED(compose
->paned
), compose
->edit_vbox
);
11186 g_object_unref(compose
->notebook
);
11187 g_object_unref(compose
->edit_vbox
);
11188 g_signal_handlers_block_by_func(G_OBJECT(widget
),
11189 G_CALLBACK(compose_grab_focus_cb
),
11191 gtk_widget_grab_focus(widget
);
11192 g_signal_handlers_unblock_by_func(G_OBJECT(widget
),
11193 G_CALLBACK(compose_grab_focus_cb
),
11199 static void compose_changed_cb(GtkTextBuffer
*textbuf
, Compose
*compose
)
11201 compose
->modified
= TRUE
;
11202 // compose_beautify_paragraph(compose, NULL, TRUE);
11203 #ifndef GENERIC_UMPC
11204 compose_set_title(compose
);
11208 static void compose_wrap_cb(GtkAction
*action
, gpointer data
)
11210 Compose
*compose
= (Compose
*)data
;
11211 compose_beautify_paragraph(compose
, NULL
, TRUE
);
11214 static void compose_wrap_all_cb(GtkAction
*action
, gpointer data
)
11216 Compose
*compose
= (Compose
*)data
;
11217 compose_wrap_all_full(compose
, TRUE
);
11220 static void compose_find_cb(GtkAction
*action
, gpointer data
)
11222 Compose
*compose
= (Compose
*)data
;
11224 message_search_compose(compose
);
11227 static void compose_toggle_autowrap_cb(GtkToggleAction
*action
,
11230 Compose
*compose
= (Compose
*)data
;
11231 compose
->autowrap
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11232 if (compose
->autowrap
)
11233 compose_wrap_all_full(compose
, TRUE
);
11234 compose
->autowrap
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11237 static void compose_toggle_autoindent_cb(GtkToggleAction
*action
,
11240 Compose
*compose
= (Compose
*)data
;
11241 compose
->autoindent
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11244 static void compose_toggle_sign_cb(GtkToggleAction
*action
, gpointer data
)
11246 Compose
*compose
= (Compose
*)data
;
11248 compose
->use_signing
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11251 static void compose_toggle_encrypt_cb(GtkToggleAction
*action
, gpointer data
)
11253 Compose
*compose
= (Compose
*)data
;
11255 compose
->use_encryption
= gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
));
11258 static void activate_privacy_system(Compose
*compose
, PrefsAccount
*account
, gboolean warn
)
11260 g_free(compose
->privacy_system
);
11261 g_free(compose
->encdata
);
11263 compose
->privacy_system
= g_strdup(account
->default_privacy_system
);
11264 compose_update_privacy_system_menu_item(compose
, warn
);
11267 static void compose_toggle_ruler_cb(GtkToggleAction
*action
, gpointer data
)
11269 Compose
*compose
= (Compose
*)data
;
11271 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
))) {
11272 gtk_widget_show(compose
->ruler_hbox
);
11273 prefs_common
.show_ruler
= TRUE
;
11275 gtk_widget_hide(compose
->ruler_hbox
);
11276 gtk_widget_queue_resize(compose
->edit_vbox
);
11277 prefs_common
.show_ruler
= FALSE
;
11281 static void compose_attach_drag_received_cb (GtkWidget
*widget
,
11282 GdkDragContext
*context
,
11285 GtkSelectionData
*data
,
11288 gpointer user_data
)
11290 Compose
*compose
= (Compose
*)user_data
;
11294 type
= gtk_selection_data_get_data_type(data
);
11295 if ((gdk_atom_name(type
) && !strcmp(gdk_atom_name(type
), "text/uri-list"))
11296 && gtk_drag_get_source_widget(context
) !=
11297 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview
)) {
11298 list
= uri_list_extract_filenames(
11299 (const gchar
*)gtk_selection_data_get_data(data
));
11300 for (tmp
= list
; tmp
!= NULL
; tmp
= tmp
->next
) {
11301 gchar
*utf8_filename
= conv_filename_to_utf8((const gchar
*)tmp
->data
);
11302 compose_attach_append
11303 (compose
, (const gchar
*)tmp
->data
,
11304 utf8_filename
, NULL
, NULL
);
11305 g_free(utf8_filename
);
11307 if (list
) compose_changed_cb(NULL
, compose
);
11308 list_free_strings(list
);
11310 } else if (gtk_drag_get_source_widget(context
)
11311 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview
)) {
11312 /* comes from our summaryview */
11313 SummaryView
* summaryview
= NULL
;
11314 GSList
* list
= NULL
, *cur
= NULL
;
11316 if (mainwindow_get_mainwindow())
11317 summaryview
= mainwindow_get_mainwindow()->summaryview
;
11320 list
= summary_get_selected_msg_list(summaryview
);
11322 for (cur
= list
; cur
; cur
= cur
->next
) {
11323 MsgInfo
*msginfo
= (MsgInfo
*)cur
->data
;
11324 gchar
*file
= NULL
;
11326 file
= procmsg_get_message_file_full(msginfo
,
11329 compose_attach_append(compose
, (const gchar
*)file
,
11330 (const gchar
*)file
, "message/rfc822", NULL
);
11334 g_slist_free(list
);
11338 static gboolean
compose_drag_drop(GtkWidget
*widget
,
11339 GdkDragContext
*drag_context
,
11341 guint time
, gpointer user_data
)
11343 /* not handling this signal makes compose_insert_drag_received_cb
11348 static gboolean completion_set_focus_to_subject
11349 (GtkWidget
*widget
,
11350 GdkEventKey
*event
,
11353 cm_return_val_if_fail(compose
!= NULL
, FALSE
);
11355 /* make backtab move to subject field */
11356 if(event
->keyval
== GDK_KEY_ISO_Left_Tab
) {
11357 gtk_widget_grab_focus(compose
->subject_entry
);
11363 static void compose_insert_drag_received_cb (GtkWidget
*widget
,
11364 GdkDragContext
*drag_context
,
11367 GtkSelectionData
*data
,
11370 gpointer user_data
)
11372 Compose
*compose
= (Compose
*)user_data
;
11376 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
11378 type
= gtk_selection_data_get_data_type(data
);
11379 if (gdk_atom_name(type
) && !strcmp(gdk_atom_name(type
), "text/uri-list")) {
11380 AlertValue val
= G_ALERTDEFAULT
;
11381 const gchar
* ddata
= (const gchar
*)gtk_selection_data_get_data(data
);
11383 list
= uri_list_extract_filenames(ddata
);
11384 if (list
== NULL
&& strstr(ddata
, "://")) {
11385 /* Assume a list of no files, and data has ://, is a remote link */
11386 gchar
*tmpdata
= g_strstrip(g_strdup(ddata
));
11387 gchar
*tmpfile
= get_tmp_file();
11388 str_write_to_file(tmpdata
, tmpfile
);
11390 compose_insert_file(compose
, tmpfile
);
11391 claws_unlink(tmpfile
);
11393 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11394 compose_beautify_paragraph(compose
, NULL
, TRUE
);
11397 switch (prefs_common
.compose_dnd_mode
) {
11398 case COMPOSE_DND_ASK
:
11399 val
= alertpanel_full(_("Insert or attach?"),
11400 _("Do you want to insert the contents of the file(s) "
11401 "into the message body, or attach it to the email?"),
11402 GTK_STOCK_CANCEL
, g_strconcat("+", _("_Insert"), NULL
), _("_Attach"),
11403 TRUE
, NULL
, ALERT_QUESTION
, G_ALERTALTERNATE
);
11405 case COMPOSE_DND_INSERT
:
11406 val
= G_ALERTALTERNATE
;
11408 case COMPOSE_DND_ATTACH
:
11409 val
= G_ALERTOTHER
;
11412 /* unexpected case */
11413 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
11416 if (val
& G_ALERTDISABLE
) {
11417 val
&= ~G_ALERTDISABLE
;
11418 /* remember what action to perform by default, only if we don't click Cancel */
11419 if (val
== G_ALERTALTERNATE
)
11420 prefs_common
.compose_dnd_mode
= COMPOSE_DND_INSERT
;
11421 else if (val
== G_ALERTOTHER
)
11422 prefs_common
.compose_dnd_mode
= COMPOSE_DND_ATTACH
;
11425 if (val
== G_ALERTDEFAULT
|| val
== G_ALERTCANCEL
) {
11426 gtk_drag_finish(drag_context
, FALSE
, FALSE
, time
);
11427 list_free_strings(list
);
11430 } else if (val
== G_ALERTOTHER
) {
11431 compose_attach_drag_received_cb(widget
, drag_context
, x
, y
, data
, info
, time
, user_data
);
11432 list_free_strings(list
);
11437 for (tmp
= list
; tmp
!= NULL
; tmp
= tmp
->next
) {
11438 compose_insert_file(compose
, (const gchar
*)tmp
->data
);
11440 list_free_strings(list
);
11442 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11447 static void compose_header_drag_received_cb (GtkWidget
*widget
,
11448 GdkDragContext
*drag_context
,
11451 GtkSelectionData
*data
,
11454 gpointer user_data
)
11456 GtkEditable
*entry
= (GtkEditable
*)user_data
;
11457 const gchar
*email
= (const gchar
*)gtk_selection_data_get_data(data
);
11459 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11462 if (!strncmp(email
, "mailto:", strlen("mailto:"))) {
11463 gchar
*decoded
=g_new(gchar
, strlen(email
));
11466 decode_uri(decoded
, email
+ strlen("mailto:")); /* will fit */
11467 gtk_editable_delete_text(entry
, 0, -1);
11468 gtk_editable_insert_text(entry
, decoded
, strlen(decoded
), &start
);
11469 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11473 gtk_drag_finish(drag_context
, TRUE
, FALSE
, time
);
11476 static void compose_toggle_return_receipt_cb(GtkToggleAction
*action
, gpointer data
)
11478 Compose
*compose
= (Compose
*)data
;
11480 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
)))
11481 compose
->return_receipt
= TRUE
;
11483 compose
->return_receipt
= FALSE
;
11486 static void compose_toggle_remove_refs_cb(GtkToggleAction
*action
, gpointer data
)
11488 Compose
*compose
= (Compose
*)data
;
11490 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action
)))
11491 compose
->remove_references
= TRUE
;
11493 compose
->remove_references
= FALSE
;
11496 static gboolean
compose_headerentry_button_clicked_cb (GtkWidget
*button
,
11497 ComposeHeaderEntry
*headerentry
)
11499 gtk_entry_set_text(GTK_ENTRY(headerentry
->entry
), "");
11503 static gboolean
compose_headerentry_key_press_event_cb(GtkWidget
*entry
,
11504 GdkEventKey
*event
,
11505 ComposeHeaderEntry
*headerentry
)
11507 if ((g_slist_length(headerentry
->compose
->header_list
) > 0) &&
11508 ((headerentry
->headernum
+ 1) != headerentry
->compose
->header_nextrow
) &&
11509 !(event
->state
& GDK_MODIFIER_MASK
) &&
11510 (event
->keyval
== GDK_KEY_BackSpace
) &&
11511 (strlen(gtk_entry_get_text(GTK_ENTRY(entry
))) == 0)) {
11512 gtk_container_remove
11513 (GTK_CONTAINER(headerentry
->compose
->header_table
),
11514 headerentry
->combo
);
11515 gtk_container_remove
11516 (GTK_CONTAINER(headerentry
->compose
->header_table
),
11517 headerentry
->entry
);
11518 headerentry
->compose
->header_list
=
11519 g_slist_remove(headerentry
->compose
->header_list
,
11521 g_free(headerentry
);
11522 } else if (event
->keyval
== GDK_KEY_Tab
) {
11523 if (headerentry
->compose
->header_last
== headerentry
) {
11524 /* Override default next focus, and give it to subject_entry
11525 * instead of notebook tabs
11527 g_signal_stop_emission_by_name(G_OBJECT(entry
), "key-press-event");
11528 gtk_widget_grab_focus(headerentry
->compose
->subject_entry
);
11535 static gboolean
scroll_postpone(gpointer data
)
11537 Compose
*compose
= (Compose
*)data
;
11539 if (compose
->batch
)
11542 GTK_EVENTS_FLUSH();
11543 compose_show_first_last_header(compose
, FALSE
);
11547 static void compose_headerentry_changed_cb(GtkWidget
*entry
,
11548 ComposeHeaderEntry
*headerentry
)
11550 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry
))) != 0) {
11551 compose_create_header_entry(headerentry
->compose
);
11552 g_signal_handlers_disconnect_matched
11553 (G_OBJECT(entry
), G_SIGNAL_MATCH_DATA
,
11554 0, 0, NULL
, NULL
, headerentry
);
11556 if (!headerentry
->compose
->batch
)
11557 g_timeout_add(0, scroll_postpone
, headerentry
->compose
);
11561 static gboolean
compose_defer_auto_save_draft(Compose
*compose
)
11563 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_UNSET
;
11564 compose_draft((gpointer
)compose
, COMPOSE_AUTO_SAVE
);
11568 static void compose_show_first_last_header(Compose
*compose
, gboolean show_first
)
11570 GtkAdjustment
*vadj
;
11572 cm_return_if_fail(compose
);
11577 cm_return_if_fail(GTK_IS_WIDGET(compose
->header_table
));
11578 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose
->header_table
)));
11579 vadj
= gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11580 gtk_widget_get_parent(compose
->header_table
)));
11581 gtk_adjustment_set_value(vadj
, (show_first
?
11582 gtk_adjustment_get_lower(vadj
) :
11583 (gtk_adjustment_get_upper(vadj
) -
11584 gtk_adjustment_get_page_size(vadj
))));
11585 gtk_adjustment_changed(vadj
);
11588 static void text_inserted(GtkTextBuffer
*buffer
, GtkTextIter
*iter
,
11589 const gchar
*text
, gint len
, Compose
*compose
)
11591 gint paste_as_quotation
= GPOINTER_TO_INT(g_object_get_data
11592 (G_OBJECT(compose
->text
), "paste_as_quotation"));
11595 cm_return_if_fail(text
!= NULL
);
11597 g_signal_handlers_block_by_func(G_OBJECT(buffer
),
11598 G_CALLBACK(text_inserted
),
11600 if (paste_as_quotation
) {
11602 const gchar
*qmark
;
11604 GtkTextIter start_iter
;
11607 len
= strlen(text
);
11609 new_text
= g_strndup(text
, len
);
11611 qmark
= compose_quote_char_from_context(compose
);
11613 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, iter
, FALSE
);
11614 gtk_text_buffer_place_cursor(buffer
, iter
);
11616 pos
= gtk_text_iter_get_offset(iter
);
11618 compose_quote_fmt(compose
, NULL
, "%Q", qmark
, new_text
, TRUE
, FALSE
,
11619 _("Quote format error at line %d."));
11620 quote_fmt_reset_vartable();
11622 g_object_set_data(G_OBJECT(compose
->text
), "paste_as_quotation",
11623 GINT_TO_POINTER(paste_as_quotation
- 1));
11625 gtk_text_buffer_get_iter_at_mark(buffer
, iter
, mark
);
11626 gtk_text_buffer_place_cursor(buffer
, iter
);
11627 gtk_text_buffer_delete_mark(buffer
, mark
);
11629 gtk_text_buffer_get_iter_at_offset(buffer
, &start_iter
, pos
);
11630 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, &start_iter
, FALSE
);
11631 compose_beautify_paragraph(compose
, &start_iter
, FALSE
);
11632 gtk_text_buffer_get_iter_at_mark(buffer
, &start_iter
, mark
);
11633 gtk_text_buffer_delete_mark(buffer
, mark
);
11635 if (strcmp(text
, "\n") || compose
->automatic_break
11636 || gtk_text_iter_starts_line(iter
)) {
11637 GtkTextIter before_ins
;
11638 gtk_text_buffer_insert(buffer
, iter
, text
, len
);
11639 if (!strstr(text
, "\n") && gtk_text_iter_has_tag(iter
, compose
->no_join_tag
)) {
11640 before_ins
= *iter
;
11641 gtk_text_iter_backward_chars(&before_ins
, len
);
11642 gtk_text_buffer_remove_tag_by_name(buffer
, "no_join", &before_ins
, iter
);
11645 /* check if the preceding is just whitespace or quote */
11646 GtkTextIter start_line
;
11647 gchar
*tmp
= NULL
, *quote
= NULL
;
11648 gint quote_len
= 0, is_normal
= 0;
11649 start_line
= *iter
;
11650 gtk_text_iter_set_line_offset(&start_line
, 0);
11651 tmp
= gtk_text_buffer_get_text(buffer
, &start_line
, iter
, FALSE
);
11654 if (*tmp
== '\0') {
11657 quote
= compose_get_quote_str(buffer
, &start_line
, "e_len
);
11665 gtk_text_buffer_insert(buffer
, iter
, text
, len
);
11667 gtk_text_buffer_insert_with_tags_by_name(buffer
,
11668 iter
, text
, len
, "no_join", NULL
);
11673 if (!paste_as_quotation
) {
11674 mark
= gtk_text_buffer_create_mark(buffer
, NULL
, iter
, FALSE
);
11675 compose_beautify_paragraph(compose
, iter
, FALSE
);
11676 gtk_text_buffer_get_iter_at_mark(buffer
, iter
, mark
);
11677 gtk_text_buffer_delete_mark(buffer
, mark
);
11680 g_signal_handlers_unblock_by_func(G_OBJECT(buffer
),
11681 G_CALLBACK(text_inserted
),
11683 g_signal_stop_emission_by_name(G_OBJECT(buffer
), "insert-text");
11685 if (compose_can_autosave(compose
) &&
11686 gtk_text_buffer_get_char_count(buffer
) % prefs_common
.autosave_length
== 0 &&
11687 compose
->draft_timeout_tag
!= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
/* disabled while loading */)
11688 compose
->draft_timeout_tag
= g_timeout_add
11689 (500, (GSourceFunc
) compose_defer_auto_save_draft
, compose
);
11693 static void compose_check_all(GtkAction
*action
, gpointer data
)
11695 Compose
*compose
= (Compose
*)data
;
11696 if (!compose
->gtkaspell
)
11699 if (gtk_widget_has_focus(compose
->subject_entry
))
11700 claws_spell_entry_check_all(
11701 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
11703 gtkaspell_check_all(compose
->gtkaspell
);
11706 static void compose_highlight_all(GtkAction
*action
, gpointer data
)
11708 Compose
*compose
= (Compose
*)data
;
11709 if (compose
->gtkaspell
) {
11710 claws_spell_entry_recheck_all(
11711 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
11712 gtkaspell_highlight_all(compose
->gtkaspell
);
11716 static void compose_check_backwards(GtkAction
*action
, gpointer data
)
11718 Compose
*compose
= (Compose
*)data
;
11719 if (!compose
->gtkaspell
) {
11720 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
11724 if (gtk_widget_has_focus(compose
->subject_entry
))
11725 claws_spell_entry_check_backwards(
11726 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
11728 gtkaspell_check_backwards(compose
->gtkaspell
);
11731 static void compose_check_forwards_go(GtkAction
*action
, gpointer data
)
11733 Compose
*compose
= (Compose
*)data
;
11734 if (!compose
->gtkaspell
) {
11735 cm_menu_set_sensitive_full(compose
->ui_manager
, "Menu/Spelling", FALSE
);
11739 if (gtk_widget_has_focus(compose
->subject_entry
))
11740 claws_spell_entry_check_forwards_go(
11741 CLAWS_SPELL_ENTRY(compose
->subject_entry
));
11743 gtkaspell_check_forwards_go(compose
->gtkaspell
);
11748 *\brief Guess originating forward account from MsgInfo and several
11749 * "common preference" settings. Return NULL if no guess.
11751 static PrefsAccount
*compose_guess_forward_account_from_msginfo(MsgInfo
*msginfo
)
11753 PrefsAccount
*account
= NULL
;
11755 cm_return_val_if_fail(msginfo
, NULL
);
11756 cm_return_val_if_fail(msginfo
->folder
, NULL
);
11757 cm_return_val_if_fail(msginfo
->folder
->prefs
, NULL
);
11759 if (msginfo
->folder
->prefs
->enable_default_account
)
11760 account
= account_find_from_id(msginfo
->folder
->prefs
->default_account
);
11763 account
= msginfo
->folder
->folder
->account
;
11765 if (!account
&& msginfo
->to
&& prefs_common
.forward_account_autosel
) {
11767 Xstrdup_a(to
, msginfo
->to
, return NULL
);
11768 extract_address(to
);
11769 account
= account_find_from_address(to
, FALSE
);
11772 if (!account
&& prefs_common
.forward_account_autosel
) {
11773 gchar cc
[BUFFSIZE
];
11774 if (!procheader_get_header_from_msginfo
11775 (msginfo
, cc
,sizeof cc
, "Cc:")) {
11776 gchar
*buf
= cc
+ strlen("Cc:");
11777 extract_address(buf
);
11778 account
= account_find_from_address(buf
, FALSE
);
11782 if (!account
&& prefs_common
.forward_account_autosel
) {
11783 gchar deliveredto
[BUFFSIZE
];
11784 if (!procheader_get_header_from_msginfo
11785 (msginfo
, deliveredto
,sizeof deliveredto
, "Delivered-To:")) {
11786 gchar
*buf
= deliveredto
+ strlen("Delivered-To:");
11787 extract_address(buf
);
11788 account
= account_find_from_address(buf
, FALSE
);
11795 gboolean
compose_close(Compose
*compose
)
11799 cm_return_val_if_fail(compose
, FALSE
);
11801 if (!g_mutex_trylock(compose
->mutex
)) {
11802 /* we have to wait for the (possibly deferred by auto-save)
11803 * drafting to be done, before destroying the compose under
11805 debug_print("waiting for drafting to finish...\n");
11806 compose_allow_user_actions(compose
, FALSE
);
11807 if (compose
->close_timeout_tag
== 0) {
11808 compose
->close_timeout_tag
=
11809 g_timeout_add (500, (GSourceFunc
) compose_close
,
11815 if (compose
->draft_timeout_tag
>= 0) {
11816 g_source_remove(compose
->draft_timeout_tag
);
11817 compose
->draft_timeout_tag
= COMPOSE_DRAFT_TIMEOUT_FORBIDDEN
;
11820 gtkut_widget_get_uposition(compose
->window
, &x
, &y
);
11821 if (!compose
->batch
) {
11822 prefs_common
.compose_x
= x
;
11823 prefs_common
.compose_y
= y
;
11825 g_mutex_unlock(compose
->mutex
);
11826 compose_destroy(compose
);
11831 * Add entry field for each address in list.
11832 * \param compose E-Mail composition object.
11833 * \param listAddress List of (formatted) E-Mail addresses.
11835 static void compose_add_field_list( Compose
*compose
, GList
*listAddress
) {
11838 node
= listAddress
;
11840 addr
= ( gchar
* ) node
->data
;
11841 compose_entry_append( compose
, addr
, COMPOSE_TO
, PREF_NONE
);
11842 node
= g_list_next( node
);
11846 static void compose_reply_from_messageview_real(MessageView
*msgview
, GSList
*msginfo_list
,
11847 guint action
, gboolean opening_multiple
)
11849 gchar
*body
= NULL
;
11850 GSList
*new_msglist
= NULL
;
11851 MsgInfo
*tmp_msginfo
= NULL
;
11852 gboolean originally_enc
= FALSE
;
11853 gboolean originally_sig
= FALSE
;
11854 Compose
*compose
= NULL
;
11855 gchar
*s_system
= NULL
;
11857 cm_return_if_fail(msgview
!= NULL
);
11859 cm_return_if_fail(msginfo_list
!= NULL
);
11861 if (g_slist_length(msginfo_list
) == 1 && !opening_multiple
) {
11862 MimeInfo
*mimeinfo
= messageview_get_selected_mime_part(msgview
);
11863 MsgInfo
*orig_msginfo
= (MsgInfo
*)msginfo_list
->data
;
11865 if (mimeinfo
!= NULL
&& mimeinfo
->type
== MIMETYPE_MESSAGE
&&
11866 !g_ascii_strcasecmp(mimeinfo
->subtype
, "rfc822")) {
11867 tmp_msginfo
= procmsg_msginfo_new_from_mimeinfo(
11868 orig_msginfo
, mimeinfo
);
11869 if (tmp_msginfo
!= NULL
) {
11870 new_msglist
= g_slist_append(NULL
, tmp_msginfo
);
11872 originally_enc
= MSG_IS_ENCRYPTED(orig_msginfo
->flags
);
11873 privacy_msginfo_get_signed_state(orig_msginfo
, &s_system
);
11874 originally_sig
= MSG_IS_SIGNED(orig_msginfo
->flags
);
11876 tmp_msginfo
->folder
= orig_msginfo
->folder
;
11877 tmp_msginfo
->msgnum
= orig_msginfo
->msgnum
;
11878 if (orig_msginfo
->tags
) {
11879 tmp_msginfo
->tags
= g_slist_copy(orig_msginfo
->tags
);
11880 tmp_msginfo
->folder
->tags_dirty
= TRUE
;
11886 if (!opening_multiple
)
11887 body
= messageview_get_selection(msgview
);
11890 compose
= compose_reply_mode((ComposeMode
)action
, new_msglist
, body
);
11891 procmsg_msginfo_free(&tmp_msginfo
);
11892 g_slist_free(new_msglist
);
11894 compose
= compose_reply_mode((ComposeMode
)action
, msginfo_list
, body
);
11896 if (compose
&& originally_enc
) {
11897 compose_force_encryption(compose
, compose
->account
, FALSE
, s_system
);
11900 if (compose
&& originally_sig
&& compose
->account
->default_sign_reply
) {
11901 compose_force_signing(compose
, compose
->account
, s_system
);
11905 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
11908 void compose_reply_from_messageview(MessageView
*msgview
, GSList
*msginfo_list
,
11911 if ((!prefs_common
.forward_as_attachment
|| action
!= COMPOSE_FORWARD
)
11912 && action
!= COMPOSE_FORWARD_AS_ATTACH
&& g_slist_length(msginfo_list
) > 1) {
11913 GSList
*cur
= msginfo_list
;
11914 gchar
*msg
= g_strdup_printf(_("You are about to reply to %d "
11915 "messages. Opening the windows "
11916 "could take some time. Do you "
11917 "want to continue?"),
11918 g_slist_length(msginfo_list
));
11919 if (g_slist_length(msginfo_list
) > 9
11920 && alertpanel(_("Warning"), msg
, GTK_STOCK_CANCEL
, "+" GTK_STOCK_YES
, NULL
)
11921 != G_ALERTALTERNATE
) {
11926 /* We'll open multiple compose windows */
11927 /* let the WM place the next windows */
11928 compose_force_window_origin
= FALSE
;
11929 for (; cur
; cur
= cur
->next
) {
11931 tmplist
.data
= cur
->data
;
11932 tmplist
.next
= NULL
;
11933 compose_reply_from_messageview_real(msgview
, &tmplist
, action
, TRUE
);
11935 compose_force_window_origin
= TRUE
;
11937 /* forwarding multiple mails as attachments is done via a
11938 * single compose window */
11939 compose_reply_from_messageview_real(msgview
, msginfo_list
, action
, FALSE
);
11943 void compose_check_for_email_account(Compose
*compose
)
11945 PrefsAccount
*ac
= NULL
, *curr
= NULL
;
11951 if (compose
->account
&& compose
->account
->protocol
== A_NNTP
) {
11952 ac
= account_get_cur_account();
11953 if (ac
->protocol
== A_NNTP
) {
11954 list
= account_get_list();
11956 for( ; list
!= NULL
; list
= g_list_next(list
)) {
11957 curr
= (PrefsAccount
*) list
->data
;
11958 if (curr
->protocol
!= A_NNTP
) {
11964 combobox_select_by_data(GTK_COMBO_BOX(compose
->account_combo
),
11969 void compose_reply_to_address(MessageView
*msgview
, MsgInfo
*msginfo
,
11970 const gchar
*address
)
11972 GSList
*msginfo_list
= NULL
;
11973 gchar
*body
= messageview_get_selection(msgview
);
11976 msginfo_list
= g_slist_prepend(msginfo_list
, msginfo
);
11978 compose
= compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS
, msginfo_list
, body
);
11979 compose_check_for_email_account(compose
);
11980 compose_set_folder_prefs(compose
, msginfo
->folder
, FALSE
);
11981 compose_entry_append(compose
, address
, COMPOSE_TO
, PREF_NONE
);
11982 compose_reply_set_subject(compose
, msginfo
);
11985 hooks_invoke(COMPOSE_CREATED_HOOKLIST
, compose
);
11988 void compose_set_position(Compose
*compose
, gint pos
)
11990 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
11992 gtkut_text_view_set_position(text
, pos
);
11995 gboolean
compose_search_string(Compose
*compose
,
11996 const gchar
*str
, gboolean case_sens
)
11998 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
12000 return gtkut_text_view_search_string(text
, str
, case_sens
);
12003 gboolean
compose_search_string_backward(Compose
*compose
,
12004 const gchar
*str
, gboolean case_sens
)
12006 GtkTextView
*text
= GTK_TEXT_VIEW(compose
->text
);
12008 return gtkut_text_view_search_string_backward(text
, str
, case_sens
);
12011 /* allocate a msginfo structure and populate its data from a compose data structure */
12012 static MsgInfo
*compose_msginfo_new_from_compose(Compose
*compose
)
12014 MsgInfo
*newmsginfo
;
12016 gchar buf
[BUFFSIZE
];
12018 cm_return_val_if_fail( compose
!= NULL
, NULL
);
12020 newmsginfo
= procmsg_msginfo_new();
12023 get_rfc822_date(buf
, sizeof(buf
));
12024 newmsginfo
->date
= g_strdup(buf
);
12027 if (compose
->from_name
) {
12028 newmsginfo
->from
= gtk_editable_get_chars(GTK_EDITABLE(compose
->from_name
), 0, -1);
12029 newmsginfo
->fromname
= procheader_get_fromname(newmsginfo
->from
);
12033 if (compose
->subject_entry
)
12034 newmsginfo
->subject
= gtk_editable_get_chars(GTK_EDITABLE(compose
->subject_entry
), 0, -1);
12036 /* to, cc, reply-to, newsgroups */
12037 for (list
= compose
->header_list
; list
; list
= list
->next
) {
12038 gchar
*header
= gtk_editable_get_chars(
12040 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry
*)list
->data
)->combo
)))), 0, -1);
12041 gchar
*entry
= gtk_editable_get_chars(
12042 GTK_EDITABLE(((ComposeHeaderEntry
*)list
->data
)->entry
), 0, -1);
12044 if ( strcasecmp(header
, prefs_common_translated_header_name("To:")) == 0 ) {
12045 if ( newmsginfo
->to
== NULL
) {
12046 newmsginfo
->to
= g_strdup(entry
);
12047 } else if (entry
&& *entry
) {
12048 gchar
*tmp
= g_strconcat(newmsginfo
->to
, ", ", entry
, NULL
);
12049 g_free(newmsginfo
->to
);
12050 newmsginfo
->to
= tmp
;
12053 if ( strcasecmp(header
, prefs_common_translated_header_name("Cc:")) == 0 ) {
12054 if ( newmsginfo
->cc
== NULL
) {
12055 newmsginfo
->cc
= g_strdup(entry
);
12056 } else if (entry
&& *entry
) {
12057 gchar
*tmp
= g_strconcat(newmsginfo
->cc
, ", ", entry
, NULL
);
12058 g_free(newmsginfo
->cc
);
12059 newmsginfo
->cc
= tmp
;
12062 if ( strcasecmp(header
,
12063 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
12064 if ( newmsginfo
->newsgroups
== NULL
) {
12065 newmsginfo
->newsgroups
= g_strdup(entry
);
12066 } else if (entry
&& *entry
) {
12067 gchar
*tmp
= g_strconcat(newmsginfo
->newsgroups
, ", ", entry
, NULL
);
12068 g_free(newmsginfo
->newsgroups
);
12069 newmsginfo
->newsgroups
= tmp
;
12077 /* other data is unset */
12083 /* update compose's dictionaries from folder dict settings */
12084 static void compose_set_dictionaries_from_folder_prefs(Compose
*compose
,
12085 FolderItem
*folder_item
)
12087 cm_return_if_fail(compose
!= NULL
);
12089 if (compose
->gtkaspell
&& folder_item
&& folder_item
->prefs
) {
12090 FolderItemPrefs
*prefs
= folder_item
->prefs
;
12092 if (prefs
->enable_default_dictionary
)
12093 gtkaspell_change_dict(compose
->gtkaspell
,
12094 prefs
->default_dictionary
, FALSE
);
12095 if (folder_item
->prefs
->enable_default_alt_dictionary
)
12096 gtkaspell_change_alt_dict(compose
->gtkaspell
,
12097 prefs
->default_alt_dictionary
);
12098 if (prefs
->enable_default_dictionary
12099 || prefs
->enable_default_alt_dictionary
)
12100 compose_spell_menu_changed(compose
);
12105 static void compose_subject_entry_activated(GtkWidget
*widget
, gpointer data
)
12107 Compose
*compose
= (Compose
*)data
;
12109 cm_return_if_fail(compose
!= NULL
);
12111 gtk_widget_grab_focus(compose
->text
);
12114 static void from_name_activate_cb(GtkWidget
*widget
, gpointer data
)
12116 gtk_combo_box_popup(GTK_COMBO_BOX(data
));