2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2009 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <pango/pango-break.h>
40 #include <sys/types.h>
46 # include <sys/wait.h>
50 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include "mainwindow.h"
63 #include "addressbook.h"
64 #include "folderview.h"
67 #include "stock_pixmap.h"
68 #include "send_message.h"
71 #include "customheader.h"
72 #include "prefs_common.h"
73 #include "prefs_account.h"
77 #include "procheader.h"
79 #include "statusbar.h"
82 #include "quoted-printable.h"
87 #include "alertpanel.h"
88 #include "manage_window.h"
89 #include "gtkshruler.h"
91 #include "addr_compl.h"
92 #include "quote_fmt.h"
94 #include "foldersel.h"
97 #include "message_search.h"
102 #include "autofaces.h"
103 #include "spell_entry.h"
115 #define N_ATTACH_COLS (N_COL_COLUMNS)
119 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
120 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
121 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
122 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
123 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
124 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
125 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
126 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
127 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
128 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
130 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
132 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
133 } ComposeCallAdvancedAction;
137 PRIORITY_HIGHEST = 1,
146 COMPOSE_INSERT_SUCCESS,
147 COMPOSE_INSERT_READ_ERROR,
148 COMPOSE_INSERT_INVALID_CHARACTER,
149 COMPOSE_INSERT_NO_FILE
150 } ComposeInsertResult;
154 COMPOSE_WRITE_FOR_SEND,
155 COMPOSE_WRITE_FOR_STORE
160 COMPOSE_QUOTE_FORCED,
167 SUBJECT_FIELD_PRESENT,
172 #define B64_LINE_SIZE 57
173 #define B64_BUFFSIZE 77
175 #define MAX_REFERENCES_LEN 999
177 static GList *compose_list = NULL;
179 static Compose *compose_generic_new (PrefsAccount *account,
182 GPtrArray *attach_files,
183 GList *listAddress );
185 static Compose *compose_create (PrefsAccount *account,
190 static void compose_entry_mark_default_to (Compose *compose,
191 const gchar *address);
192 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
193 ComposeQuoteMode quote_mode,
197 static Compose *compose_forward_multiple (PrefsAccount *account,
198 GSList *msginfo_list);
199 static Compose *compose_reply (MsgInfo *msginfo,
200 ComposeQuoteMode quote_mode,
205 static Compose *compose_reply_mode (ComposeMode mode,
206 GSList *msginfo_list,
208 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
209 static void compose_update_privacy_systems_menu(Compose *compose);
211 static GtkWidget *compose_account_option_menu_create
213 static void compose_set_out_encoding (Compose *compose);
214 static void compose_set_template_menu (Compose *compose);
215 static void compose_destroy (Compose *compose);
217 static MailField compose_entries_set (Compose *compose,
219 ComposeEntryType to_type);
220 static gint compose_parse_header (Compose *compose,
222 static gchar *compose_parse_references (const gchar *ref,
225 static gchar *compose_quote_fmt (Compose *compose,
231 gboolean need_unescape,
232 const gchar *err_msg);
234 static void compose_reply_set_entry (Compose *compose,
240 followup_and_reply_to);
241 static void compose_reedit_set_entry (Compose *compose,
244 static void compose_insert_sig (Compose *compose,
246 static ComposeInsertResult compose_insert_file (Compose *compose,
249 static gboolean compose_attach_append (Compose *compose,
252 const gchar *content_type);
253 static void compose_attach_parts (Compose *compose,
256 static gboolean compose_beautify_paragraph (Compose *compose,
257 GtkTextIter *par_iter,
259 static void compose_wrap_all (Compose *compose);
260 static void compose_wrap_all_full (Compose *compose,
263 static void compose_set_title (Compose *compose);
264 static void compose_select_account (Compose *compose,
265 PrefsAccount *account,
268 static PrefsAccount *compose_current_mail_account(void);
269 /* static gint compose_send (Compose *compose); */
270 static gboolean compose_check_for_valid_recipient
272 static gboolean compose_check_entries (Compose *compose,
273 gboolean check_everything);
274 static gint compose_write_to_file (Compose *compose,
277 gboolean attach_parts);
278 static gint compose_write_body_to_file (Compose *compose,
280 static gint compose_remove_reedit_target (Compose *compose,
282 static void compose_remove_draft (Compose *compose);
283 static gint compose_queue_sub (Compose *compose,
287 gboolean check_subject,
288 gboolean remove_reedit_target);
289 static int compose_add_attachments (Compose *compose,
291 static gchar *compose_get_header (Compose *compose);
293 static void compose_convert_header (Compose *compose,
298 gboolean addr_field);
300 static void compose_attach_info_free (AttachInfo *ainfo);
301 static void compose_attach_remove_selected (GtkAction *action,
304 static void compose_template_apply (Compose *compose,
307 static void compose_attach_property (GtkAction *action,
309 static void compose_attach_property_create (gboolean *cancelled);
310 static void attach_property_ok (GtkWidget *widget,
311 gboolean *cancelled);
312 static void attach_property_cancel (GtkWidget *widget,
313 gboolean *cancelled);
314 static gint attach_property_delete_event (GtkWidget *widget,
316 gboolean *cancelled);
317 static gboolean attach_property_key_pressed (GtkWidget *widget,
319 gboolean *cancelled);
321 static void compose_exec_ext_editor (Compose *compose);
323 static gint compose_exec_ext_editor_real (const gchar *file);
324 static gboolean compose_ext_editor_kill (Compose *compose);
325 static gboolean compose_input_cb (GIOChannel *source,
326 GIOCondition condition,
328 static void compose_set_ext_editor_sensitive (Compose *compose,
330 #endif /* G_OS_UNIX */
332 static void compose_undo_state_changed (UndoMain *undostruct,
337 static void compose_create_header_entry (Compose *compose);
338 static void compose_add_header_entry (Compose *compose, const gchar *header,
339 gchar *text, ComposePrefType pref_type);
340 static void compose_remove_header_entries(Compose *compose);
342 static void compose_update_priority_menu_item(Compose * compose);
344 static void compose_spell_menu_changed (void *data);
345 static void compose_dict_changed (void *data);
347 static void compose_add_field_list ( Compose *compose,
348 GList *listAddress );
350 /* callback functions */
352 static gboolean compose_edit_size_alloc (GtkEditable *widget,
353 GtkAllocation *allocation,
354 GtkSHRuler *shruler);
355 static void account_activated (GtkComboBox *optmenu,
357 static void attach_selected (GtkTreeView *tree_view,
358 GtkTreePath *tree_path,
359 GtkTreeViewColumn *column,
361 static gboolean attach_button_pressed (GtkWidget *widget,
362 GdkEventButton *event,
364 static gboolean attach_key_pressed (GtkWidget *widget,
367 static void compose_send_cb (GtkAction *action, gpointer data);
368 static void compose_send_later_cb (GtkAction *action, gpointer data);
370 static void compose_save_cb (GtkAction *action,
373 static void compose_attach_cb (GtkAction *action,
375 static void compose_insert_file_cb (GtkAction *action,
377 static void compose_insert_sig_cb (GtkAction *action,
380 static void compose_close_cb (GtkAction *action,
383 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
385 static void compose_address_cb (GtkAction *action,
387 static void about_show_cb (GtkAction *action,
389 static void compose_template_activate_cb(GtkWidget *widget,
392 static void compose_ext_editor_cb (GtkAction *action,
395 static gint compose_delete_cb (GtkWidget *widget,
399 static void compose_undo_cb (GtkAction *action,
401 static void compose_redo_cb (GtkAction *action,
403 static void compose_cut_cb (GtkAction *action,
405 static void compose_copy_cb (GtkAction *action,
407 static void compose_paste_cb (GtkAction *action,
409 static void compose_paste_as_quote_cb (GtkAction *action,
411 static void compose_paste_no_wrap_cb (GtkAction *action,
413 static void compose_paste_wrap_cb (GtkAction *action,
415 static void compose_allsel_cb (GtkAction *action,
418 static void compose_advanced_action_cb (GtkAction *action,
421 static void compose_grab_focus_cb (GtkWidget *widget,
424 static void compose_changed_cb (GtkTextBuffer *textbuf,
427 static void compose_wrap_cb (GtkAction *action,
429 static void compose_wrap_all_cb (GtkAction *action,
431 static void compose_find_cb (GtkAction *action,
433 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
435 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
438 static void compose_toggle_ruler_cb (GtkToggleAction *action,
440 static void compose_toggle_sign_cb (GtkToggleAction *action,
442 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
444 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
445 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
446 static void activate_privacy_system (Compose *compose,
447 PrefsAccount *account,
449 static void compose_use_signing(Compose *compose, gboolean use_signing);
450 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
451 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
453 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
455 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
456 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
457 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
459 static void compose_attach_drag_received_cb (GtkWidget *widget,
460 GdkDragContext *drag_context,
463 GtkSelectionData *data,
467 static void compose_insert_drag_received_cb (GtkWidget *widget,
468 GdkDragContext *drag_context,
471 GtkSelectionData *data,
475 static void compose_header_drag_received_cb (GtkWidget *widget,
476 GdkDragContext *drag_context,
479 GtkSelectionData *data,
484 static gboolean compose_drag_drop (GtkWidget *widget,
485 GdkDragContext *drag_context,
487 guint time, gpointer user_data);
489 static void text_inserted (GtkTextBuffer *buffer,
494 static Compose *compose_generic_reply(MsgInfo *msginfo,
495 ComposeQuoteMode quote_mode,
499 gboolean followup_and_reply_to,
502 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
503 ComposeHeaderEntry *headerentry);
504 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
506 ComposeHeaderEntry *headerentry);
507 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
508 ComposeHeaderEntry *headerentry);
510 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
512 static void compose_allow_user_actions (Compose *compose, gboolean allow);
514 static void compose_nothing_cb (GtkAction *action, gpointer data)
520 static void compose_check_all (GtkAction *action, gpointer data);
521 static void compose_highlight_all (GtkAction *action, gpointer data);
522 static void compose_check_backwards (GtkAction *action, gpointer data);
523 static void compose_check_forwards_go (GtkAction *action, gpointer data);
526 static gint compose_defer_auto_save_draft (Compose *compose);
527 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
529 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
532 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
533 FolderItem *folder_item);
535 static void compose_attach_update_label(Compose *compose);
537 static GtkActionEntry compose_popup_entries[] =
539 {"Compose", NULL, "Compose" },
540 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
541 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
542 {"Compose/---", NULL, "---", NULL, NULL, NULL },
543 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
546 static GtkActionEntry compose_entries[] =
548 {"Menu", NULL, "Menu" },
550 {"Message", NULL, N_("_Message") },
551 {"Edit", NULL, N_("_Edit") },
553 {"Spelling", NULL, N_("_Spelling") },
555 {"Options", NULL, N_("_Options") },
556 {"Tools", NULL, N_("_Tools") },
557 {"Help", NULL, N_("_Help") },
559 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
560 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
561 {"Message/---", NULL, "---" },
563 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
564 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
565 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
566 /* {"Message/---", NULL, "---" }, */
567 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
568 /* {"Message/---", NULL, "---" }, */
569 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
572 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
573 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
574 {"Edit/---", NULL, "---" },
576 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
577 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
578 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
580 {"Edit/SpecialPaste", NULL, N_("Special paste") },
581 {"Edit/SpecialPaste/AsQuotation", NULL, N_("as _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
582 {"Edit/SpecialPaste/Wrapped", NULL, N_("_wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
583 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
585 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
587 {"Edit/Advanced", NULL, N_("A_dvanced") },
588 {"Edit/Advanced/BackChar", NULL, N_("Move a character backward"), "<shift><control>B", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER*/
589 {"Edit/Advanced/ForwChar", NULL, N_("Move a character forward"), "<shift><control>F", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER*/
590 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
591 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
592 {"Edit/Advanced/BegLine", NULL, N_("Move to beginning of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE*/
593 {"Edit/Advanced/EndLine", NULL, N_("Move to end of line"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE*/
594 {"Edit/Advanced/PrevLine", NULL, N_("Move to previous line"), "<control>P", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE*/
595 {"Edit/Advanced/NextLine", NULL, N_("Move to next line"), "<control>N", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE*/
596 {"Edit/Advanced/DelBackChar", NULL, N_("Delete a character backward"), "<control>H", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER*/
597 {"Edit/Advanced/DelForwChar", NULL, N_("Delete a character forward"), "<control>D", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER*/
598 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
599 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
600 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
601 {"Edit/Advanced/DelEndLine", NULL, N_("Delete to end of line"), "<control>K", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END*/
603 /* {"Edit/---", NULL, "---" }, */
604 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
606 /* {"Edit/---", NULL, "---" }, */
607 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
608 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
609 /* {"Edit/---", NULL, "---" }, */
610 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
613 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
614 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
615 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
616 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
618 {"Spelling/---", NULL, "---" },
619 {"Spelling/Options", NULL, N_("_Options") },
624 {"Options/ReplyMode", NULL, N_("Reply _mode") },
625 {"Options/---", NULL, "---" },
626 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
627 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
629 /* {"Options/---", NULL, "---" }, */
631 {"Options/Priority", NULL, N_("_Priority") },
633 {"Options/Encoding", NULL, N_("Character _encoding") },
634 {"Options/Encoding/---", NULL, "---" },
635 #define ENC_ACTION(cs_char,c_char,string) \
636 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
638 {"Options/Encoding/Western", NULL, N_("Western European") },
639 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
640 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
641 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
642 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
643 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
644 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
645 {"Options/Encoding/Korean", NULL, N_("Korean") },
646 {"Options/Encoding/Thai", NULL, N_("Thai") },
649 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
651 {"Tools/Template", NULL, N_("_Template") },
652 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
653 {"Tools/Actions", NULL, N_("Actio_ns") },
654 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
657 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
660 static GtkToggleActionEntry compose_toggle_entries[] =
662 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
663 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
664 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
665 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
666 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
667 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
668 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
671 static GtkRadioActionEntry compose_radio_rm_entries[] =
673 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
674 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
675 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
676 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
679 static GtkRadioActionEntry compose_radio_prio_entries[] =
681 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
682 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
683 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
684 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
685 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
688 static GtkRadioActionEntry compose_radio_enc_entries[] =
690 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
691 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
692 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
693 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
694 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
695 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
696 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
697 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
698 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
699 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
700 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
701 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
702 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
703 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
704 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
705 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
706 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
707 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
708 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
709 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
710 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
711 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
712 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
713 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
714 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
715 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
716 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
723 static GtkTargetEntry compose_mime_types[] =
725 {"text/uri-list", 0, 0},
726 {"UTF8_STRING", 0, 0},
730 static gboolean compose_put_existing_to_front(MsgInfo *info)
732 GList *compose_list = compose_get_compose_list();
736 for (elem = compose_list; elem != NULL && elem->data != NULL;
738 Compose *c = (Compose*)elem->data;
740 if (!c->targetinfo || !c->targetinfo->msgid ||
744 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
745 gtkut_window_popup(c->window);
753 static GdkColor quote_color1 =
754 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
755 static GdkColor quote_color2 =
756 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
757 static GdkColor quote_color3 =
758 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
760 static GdkColor quote_bgcolor1 =
761 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
762 static GdkColor quote_bgcolor2 =
763 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
764 static GdkColor quote_bgcolor3 =
765 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
767 static GdkColor signature_color = {
774 static GdkColor uri_color = {
781 static void compose_create_tags(GtkTextView *text, Compose *compose)
783 GtkTextBuffer *buffer;
784 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
790 buffer = gtk_text_view_get_buffer(text);
792 if (prefs_common.enable_color) {
793 /* grab the quote colors, converting from an int to a GdkColor */
794 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
796 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
798 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
800 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
802 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
804 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
806 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
808 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
811 signature_color = quote_color1 = quote_color2 = quote_color3 =
812 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
815 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
816 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
817 "foreground-gdk", "e_color1,
818 "paragraph-background-gdk", "e_bgcolor1,
820 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
821 "foreground-gdk", "e_color2,
822 "paragraph-background-gdk", "e_bgcolor2,
824 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
825 "foreground-gdk", "e_color3,
826 "paragraph-background-gdk", "e_bgcolor3,
829 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
830 "foreground-gdk", "e_color1,
832 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
833 "foreground-gdk", "e_color2,
835 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
836 "foreground-gdk", "e_color3,
840 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
841 "foreground-gdk", &signature_color,
844 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
845 "foreground-gdk", &uri_color,
847 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
848 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
850 color[0] = quote_color1;
851 color[1] = quote_color2;
852 color[2] = quote_color3;
853 color[3] = quote_bgcolor1;
854 color[4] = quote_bgcolor2;
855 color[5] = quote_bgcolor3;
856 color[6] = signature_color;
857 color[7] = uri_color;
858 cmap = gdk_drawable_get_colormap(compose->window->window);
859 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
861 for (i = 0; i < 8; i++) {
862 if (success[i] == FALSE) {
865 g_warning("Compose: color allocation failed.\n");
866 style = gtk_widget_get_style(GTK_WIDGET(text));
867 quote_color1 = quote_color2 = quote_color3 =
868 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
869 signature_color = uri_color = black;
874 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
875 GPtrArray *attach_files)
877 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
880 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
882 return compose_generic_new(account, mailto, item, NULL, NULL);
885 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
887 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
890 #define SCROLL_TO_CURSOR(compose) { \
891 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
892 gtk_text_view_get_buffer( \
893 GTK_TEXT_VIEW(compose->text))); \
894 gtk_text_view_scroll_mark_onscreen( \
895 GTK_TEXT_VIEW(compose->text), \
899 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
902 if (folderidentifier) {
903 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
904 prefs_common.compose_save_to_history = add_history(
905 prefs_common.compose_save_to_history, folderidentifier);
906 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
907 prefs_common.compose_save_to_history);
910 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
911 if (folderidentifier)
912 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
914 gtk_entry_set_text(GTK_ENTRY(entry), "");
917 static gchar *compose_get_save_to(Compose *compose)
920 gchar *result = NULL;
921 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
922 result = gtk_editable_get_chars(entry, 0, -1);
925 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
926 prefs_common.compose_save_to_history = add_history(
927 prefs_common.compose_save_to_history, result);
928 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
929 prefs_common.compose_save_to_history);
934 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
935 GPtrArray *attach_files, GList *listAddress )
938 GtkTextView *textview;
939 GtkTextBuffer *textbuf;
941 const gchar *subject_format = NULL;
942 const gchar *body_format = NULL;
943 gchar *mailto_from = NULL;
944 PrefsAccount *mailto_account = NULL;
945 MsgInfo* dummyinfo = NULL;
946 MailField mfield = NO_FIELD_PRESENT;
950 /* check if mailto defines a from */
951 if (mailto && *mailto != '\0') {
952 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
953 /* mailto defines a from, check if we can get account prefs from it,
954 if not, the account prefs will be guessed using other ways, but we'll keep
957 mailto_account = account_find_from_address(mailto_from, TRUE);
959 account = mailto_account;
962 /* if no account prefs set from mailto, set if from folder prefs (if any) */
963 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
964 account = account_find_from_id(item->prefs->default_account);
966 /* if no account prefs set, fallback to the current one */
967 if (!account) account = cur_account;
968 cm_return_val_if_fail(account != NULL, NULL);
970 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
972 /* override from name if mailto asked for it */
974 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
977 /* override from name according to folder properties */
978 if (item && item->prefs &&
979 item->prefs->compose_with_format &&
980 item->prefs->compose_override_from_format &&
981 *item->prefs->compose_override_from_format != '\0') {
986 dummyinfo = compose_msginfo_new_from_compose(compose);
988 /* decode \-escape sequences in the internal representation of the quote format */
989 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
990 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
993 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
996 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
998 quote_fmt_scan_string(tmp);
1001 buf = quote_fmt_get_buffer();
1003 alertpanel_error(_("New message From format error."));
1005 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1006 quote_fmt_reset_vartable();
1011 compose->replyinfo = NULL;
1012 compose->fwdinfo = NULL;
1014 textview = GTK_TEXT_VIEW(compose->text);
1015 textbuf = gtk_text_view_get_buffer(textview);
1016 compose_create_tags(textview, compose);
1018 undo_block(compose->undostruct);
1020 compose_set_dictionaries_from_folder_prefs(compose, item);
1023 if (account->auto_sig)
1024 compose_insert_sig(compose, FALSE);
1025 gtk_text_buffer_get_start_iter(textbuf, &iter);
1026 gtk_text_buffer_place_cursor(textbuf, &iter);
1028 if (account->protocol != A_NNTP) {
1029 if (mailto && *mailto != '\0') {
1030 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1032 } else if (item && item->prefs) {
1033 if (item->prefs->enable_default_bcc) {
1034 compose_entry_append(compose, item->prefs->default_bcc,
1035 COMPOSE_BCC, PREF_FOLDER);
1037 if (item->prefs->enable_default_cc) {
1038 compose_entry_append(compose, item->prefs->default_cc,
1039 COMPOSE_CC, PREF_FOLDER);
1041 if (item->prefs->enable_default_replyto) {
1042 compose_entry_append(compose, item->prefs->default_replyto,
1043 COMPOSE_REPLYTO, PREF_FOLDER);
1045 if (item->prefs->enable_default_to) {
1046 compose_entry_append(compose, item->prefs->default_to,
1047 COMPOSE_TO, PREF_FOLDER);
1048 compose_entry_mark_default_to(compose, item->prefs->default_to);
1051 if (item && item->ret_rcpt) {
1052 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1055 if (mailto && *mailto != '\0') {
1056 if (!strchr(mailto, '@'))
1057 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1059 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1060 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1061 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1064 * CLAWS: just don't allow return receipt request, even if the user
1065 * may want to send an email. simple but foolproof.
1067 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1069 compose_add_field_list( compose, listAddress );
1071 if (item && item->prefs && item->prefs->compose_with_format) {
1072 subject_format = item->prefs->compose_subject_format;
1073 body_format = item->prefs->compose_body_format;
1074 } else if (account->compose_with_format) {
1075 subject_format = account->compose_subject_format;
1076 body_format = account->compose_body_format;
1077 } else if (prefs_common.compose_with_format) {
1078 subject_format = prefs_common.compose_subject_format;
1079 body_format = prefs_common.compose_body_format;
1082 if (subject_format || body_format) {
1085 && *subject_format != '\0' )
1087 gchar *subject = NULL;
1092 dummyinfo = compose_msginfo_new_from_compose(compose);
1094 /* decode \-escape sequences in the internal representation of the quote format */
1095 tmp = malloc(strlen(subject_format)+1);
1096 pref_get_unescaped_pref(tmp, subject_format);
1098 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1100 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1101 compose->gtkaspell);
1103 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1105 quote_fmt_scan_string(tmp);
1108 buf = quote_fmt_get_buffer();
1110 alertpanel_error(_("New message subject format error."));
1112 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1113 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1114 quote_fmt_reset_vartable();
1118 mfield = SUBJECT_FIELD_PRESENT;
1122 && *body_format != '\0' )
1125 GtkTextBuffer *buffer;
1126 GtkTextIter start, end;
1130 dummyinfo = compose_msginfo_new_from_compose(compose);
1132 text = GTK_TEXT_VIEW(compose->text);
1133 buffer = gtk_text_view_get_buffer(text);
1134 gtk_text_buffer_get_start_iter(buffer, &start);
1135 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1136 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1138 compose_quote_fmt(compose, dummyinfo,
1140 NULL, tmp, FALSE, TRUE,
1141 _("The body of the \"New message\" template has an error at line %d."));
1142 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1143 quote_fmt_reset_vartable();
1147 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1148 gtkaspell_highlight_all(compose->gtkaspell);
1150 mfield = BODY_FIELD_PRESENT;
1154 procmsg_msginfo_free( dummyinfo );
1160 for (i = 0; i < attach_files->len; i++) {
1161 file = g_ptr_array_index(attach_files, i);
1162 compose_attach_append(compose, file, file, NULL);
1166 compose_show_first_last_header(compose, TRUE);
1168 /* Set save folder */
1169 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1170 gchar *folderidentifier;
1172 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1173 folderidentifier = folder_item_get_identifier(item);
1174 compose_set_save_to(compose, folderidentifier);
1175 g_free(folderidentifier);
1178 /* Place cursor according to provided input (mfield) */
1180 case NO_FIELD_PRESENT:
1181 if (compose->header_last)
1182 gtk_widget_grab_focus(compose->header_last->entry);
1184 case TO_FIELD_PRESENT:
1185 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1187 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1190 gtk_widget_grab_focus(compose->subject_entry);
1192 case SUBJECT_FIELD_PRESENT:
1193 textview = GTK_TEXT_VIEW(compose->text);
1196 textbuf = gtk_text_view_get_buffer(textview);
1199 mark = gtk_text_buffer_get_insert(textbuf);
1200 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1201 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1203 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1204 * only defers where it comes to the variable body
1205 * is not null. If no body is present compose->text
1206 * will be null in which case you cannot place the
1207 * cursor inside the component so. An empty component
1208 * is therefore created before placing the cursor
1210 case BODY_FIELD_PRESENT:
1211 gtk_widget_grab_focus(compose->text);
1215 undo_unblock(compose->undostruct);
1217 if (prefs_common.auto_exteditor)
1218 compose_exec_ext_editor(compose);
1220 compose->draft_timeout_tag = -1;
1221 SCROLL_TO_CURSOR(compose);
1223 compose->modified = FALSE;
1224 compose_set_title(compose);
1228 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1229 gboolean override_pref, const gchar *system)
1231 const gchar *privacy = NULL;
1233 cm_return_if_fail(compose != NULL);
1234 cm_return_if_fail(account != NULL);
1236 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1241 else if (account->default_privacy_system
1242 && strlen(account->default_privacy_system)) {
1243 privacy = account->default_privacy_system;
1245 GSList *privacy_avail = privacy_get_system_ids();
1246 if (privacy_avail && g_slist_length(privacy_avail)) {
1247 privacy = (gchar *)(privacy_avail->data);
1250 if (privacy != NULL) {
1252 g_free(compose->privacy_system);
1253 compose->privacy_system = NULL;
1255 if (compose->privacy_system == NULL)
1256 compose->privacy_system = g_strdup(privacy);
1257 else if (*(compose->privacy_system) == '\0') {
1258 g_free(compose->privacy_system);
1259 compose->privacy_system = g_strdup(privacy);
1261 compose_update_privacy_system_menu_item(compose, FALSE);
1262 compose_use_encryption(compose, TRUE);
1266 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1268 const gchar *privacy = NULL;
1272 else if (account->default_privacy_system
1273 && strlen(account->default_privacy_system)) {
1274 privacy = account->default_privacy_system;
1276 GSList *privacy_avail = privacy_get_system_ids();
1277 if (privacy_avail && g_slist_length(privacy_avail)) {
1278 privacy = (gchar *)(privacy_avail->data);
1282 if (privacy != NULL) {
1284 g_free(compose->privacy_system);
1285 compose->privacy_system = NULL;
1287 if (compose->privacy_system == NULL)
1288 compose->privacy_system = g_strdup(privacy);
1289 compose_update_privacy_system_menu_item(compose, FALSE);
1290 compose_use_signing(compose, TRUE);
1294 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1298 Compose *compose = NULL;
1300 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1302 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1303 cm_return_val_if_fail(msginfo != NULL, NULL);
1305 list_len = g_slist_length(msginfo_list);
1309 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1310 FALSE, prefs_common.default_reply_list, FALSE, body);
1312 case COMPOSE_REPLY_WITH_QUOTE:
1313 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1314 FALSE, prefs_common.default_reply_list, FALSE, body);
1316 case COMPOSE_REPLY_WITHOUT_QUOTE:
1317 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1318 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1320 case COMPOSE_REPLY_TO_SENDER:
1321 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1322 FALSE, FALSE, TRUE, body);
1324 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1325 compose = compose_followup_and_reply_to(msginfo,
1326 COMPOSE_QUOTE_CHECK,
1327 FALSE, FALSE, body);
1329 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1330 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1331 FALSE, FALSE, TRUE, body);
1333 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1334 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1335 FALSE, FALSE, TRUE, NULL);
1337 case COMPOSE_REPLY_TO_ALL:
1338 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1339 TRUE, FALSE, FALSE, body);
1341 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1342 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1343 TRUE, FALSE, FALSE, body);
1345 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1346 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1347 TRUE, FALSE, FALSE, NULL);
1349 case COMPOSE_REPLY_TO_LIST:
1350 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1351 FALSE, TRUE, FALSE, body);
1353 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1354 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1355 FALSE, TRUE, FALSE, body);
1357 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1358 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1359 FALSE, TRUE, FALSE, NULL);
1361 case COMPOSE_FORWARD:
1362 if (prefs_common.forward_as_attachment) {
1363 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1366 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1370 case COMPOSE_FORWARD_INLINE:
1371 /* check if we reply to more than one Message */
1372 if (list_len == 1) {
1373 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1376 /* more messages FALL THROUGH */
1377 case COMPOSE_FORWARD_AS_ATTACH:
1378 compose = compose_forward_multiple(NULL, msginfo_list);
1380 case COMPOSE_REDIRECT:
1381 compose = compose_redirect(NULL, msginfo, FALSE);
1384 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1387 if (compose == NULL) {
1388 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1392 compose->rmode = mode;
1393 switch (compose->rmode) {
1395 case COMPOSE_REPLY_WITH_QUOTE:
1396 case COMPOSE_REPLY_WITHOUT_QUOTE:
1397 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1398 debug_print("reply mode Normal\n");
1399 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1400 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1402 case COMPOSE_REPLY_TO_SENDER:
1403 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1404 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1405 debug_print("reply mode Sender\n");
1406 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1408 case COMPOSE_REPLY_TO_ALL:
1409 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1410 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1411 debug_print("reply mode All\n");
1412 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1414 case COMPOSE_REPLY_TO_LIST:
1415 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1416 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1417 debug_print("reply mode List\n");
1418 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1426 static Compose *compose_reply(MsgInfo *msginfo,
1427 ComposeQuoteMode quote_mode,
1433 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1434 to_sender, FALSE, body);
1437 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1438 ComposeQuoteMode quote_mode,
1443 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1444 to_sender, TRUE, body);
1447 static void compose_extract_original_charset(Compose *compose)
1449 MsgInfo *info = NULL;
1450 if (compose->replyinfo) {
1451 info = compose->replyinfo;
1452 } else if (compose->fwdinfo) {
1453 info = compose->fwdinfo;
1454 } else if (compose->targetinfo) {
1455 info = compose->targetinfo;
1458 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1459 MimeInfo *partinfo = mimeinfo;
1460 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1461 partinfo = procmime_mimeinfo_next(partinfo);
1463 compose->orig_charset =
1464 g_strdup(procmime_mimeinfo_get_parameter(
1465 partinfo, "charset"));
1467 procmime_mimeinfo_free_all(mimeinfo);
1471 #define SIGNAL_BLOCK(buffer) { \
1472 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1473 G_CALLBACK(compose_changed_cb), \
1475 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1476 G_CALLBACK(text_inserted), \
1480 #define SIGNAL_UNBLOCK(buffer) { \
1481 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1482 G_CALLBACK(compose_changed_cb), \
1484 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1485 G_CALLBACK(text_inserted), \
1489 static Compose *compose_generic_reply(MsgInfo *msginfo,
1490 ComposeQuoteMode quote_mode,
1491 gboolean to_all, gboolean to_ml,
1493 gboolean followup_and_reply_to,
1497 PrefsAccount *account = NULL;
1498 GtkTextView *textview;
1499 GtkTextBuffer *textbuf;
1500 gboolean quote = FALSE;
1501 const gchar *qmark = NULL;
1502 const gchar *body_fmt = NULL;
1503 gchar *s_system = NULL;
1505 cm_return_val_if_fail(msginfo != NULL, NULL);
1506 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1508 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1510 cm_return_val_if_fail(account != NULL, NULL);
1512 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1514 compose->updating = TRUE;
1516 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1517 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1519 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1520 if (!compose->replyinfo)
1521 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1523 compose_extract_original_charset(compose);
1525 if (msginfo->folder && msginfo->folder->ret_rcpt)
1526 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1528 /* Set save folder */
1529 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1530 gchar *folderidentifier;
1532 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1533 folderidentifier = folder_item_get_identifier(msginfo->folder);
1534 compose_set_save_to(compose, folderidentifier);
1535 g_free(folderidentifier);
1538 if (compose_parse_header(compose, msginfo) < 0) {
1539 compose->updating = FALSE;
1540 compose_destroy(compose);
1544 /* override from name according to folder properties */
1545 if (msginfo->folder && msginfo->folder->prefs &&
1546 msginfo->folder->prefs->reply_with_format &&
1547 msginfo->folder->prefs->reply_override_from_format &&
1548 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1553 /* decode \-escape sequences in the internal representation of the quote format */
1554 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1555 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1558 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1559 compose->gtkaspell);
1561 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1563 quote_fmt_scan_string(tmp);
1566 buf = quote_fmt_get_buffer();
1568 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1570 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1571 quote_fmt_reset_vartable();
1576 textview = (GTK_TEXT_VIEW(compose->text));
1577 textbuf = gtk_text_view_get_buffer(textview);
1578 compose_create_tags(textview, compose);
1580 undo_block(compose->undostruct);
1582 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1585 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1586 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1587 /* use the reply format of folder (if enabled), or the account's one
1588 (if enabled) or fallback to the global reply format, which is always
1589 enabled (even if empty), and use the relevant quotemark */
1591 if (msginfo->folder && msginfo->folder->prefs &&
1592 msginfo->folder->prefs->reply_with_format) {
1593 qmark = msginfo->folder->prefs->reply_quotemark;
1594 body_fmt = msginfo->folder->prefs->reply_body_format;
1596 } else if (account->reply_with_format) {
1597 qmark = account->reply_quotemark;
1598 body_fmt = account->reply_body_format;
1601 qmark = prefs_common.quotemark;
1602 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1603 body_fmt = gettext(prefs_common.quotefmt);
1610 /* empty quotemark is not allowed */
1611 if (qmark == NULL || *qmark == '\0')
1613 compose_quote_fmt(compose, compose->replyinfo,
1614 body_fmt, qmark, body, FALSE, TRUE,
1615 _("The body of the \"Reply\" template has an error at line %d."));
1616 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1617 quote_fmt_reset_vartable();
1619 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1620 gtkaspell_highlight_all(compose->gtkaspell);
1624 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1625 compose_force_encryption(compose, account, FALSE, s_system);
1628 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1629 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1630 compose_force_signing(compose, account, s_system);
1634 SIGNAL_BLOCK(textbuf);
1636 if (account->auto_sig)
1637 compose_insert_sig(compose, FALSE);
1639 compose_wrap_all(compose);
1641 SIGNAL_UNBLOCK(textbuf);
1643 gtk_widget_grab_focus(compose->text);
1645 undo_unblock(compose->undostruct);
1647 if (prefs_common.auto_exteditor)
1648 compose_exec_ext_editor(compose);
1650 compose->modified = FALSE;
1651 compose_set_title(compose);
1653 compose->updating = FALSE;
1654 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1655 SCROLL_TO_CURSOR(compose);
1657 if (compose->deferred_destroy) {
1658 compose_destroy(compose);
1665 #define INSERT_FW_HEADER(var, hdr) \
1666 if (msginfo->var && *msginfo->var) { \
1667 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1668 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1669 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1672 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1673 gboolean as_attach, const gchar *body,
1674 gboolean no_extedit,
1678 GtkTextView *textview;
1679 GtkTextBuffer *textbuf;
1682 cm_return_val_if_fail(msginfo != NULL, NULL);
1683 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1686 !(account = compose_guess_forward_account_from_msginfo
1688 account = cur_account;
1690 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1692 compose->updating = TRUE;
1693 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1694 if (!compose->fwdinfo)
1695 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1697 compose_extract_original_charset(compose);
1699 if (msginfo->subject && *msginfo->subject) {
1700 gchar *buf, *buf2, *p;
1702 buf = p = g_strdup(msginfo->subject);
1703 p += subject_get_prefix_length(p);
1704 memmove(buf, p, strlen(p) + 1);
1706 buf2 = g_strdup_printf("Fw: %s", buf);
1707 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1713 /* override from name according to folder properties */
1714 if (msginfo->folder && msginfo->folder->prefs &&
1715 msginfo->folder->prefs->forward_with_format &&
1716 msginfo->folder->prefs->forward_override_from_format &&
1717 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1721 MsgInfo *full_msginfo = NULL;
1724 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1726 full_msginfo = procmsg_msginfo_copy(msginfo);
1728 /* decode \-escape sequences in the internal representation of the quote format */
1729 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1730 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1733 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1734 compose->gtkaspell);
1736 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1738 quote_fmt_scan_string(tmp);
1741 buf = quote_fmt_get_buffer();
1743 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1745 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1746 quote_fmt_reset_vartable();
1749 procmsg_msginfo_free(full_msginfo);
1752 textview = GTK_TEXT_VIEW(compose->text);
1753 textbuf = gtk_text_view_get_buffer(textview);
1754 compose_create_tags(textview, compose);
1756 undo_block(compose->undostruct);
1760 msgfile = procmsg_get_message_file(msginfo);
1761 if (!is_file_exist(msgfile))
1762 g_warning("%s: file not exist\n", msgfile);
1764 compose_attach_append(compose, msgfile, msgfile,
1769 const gchar *qmark = NULL;
1770 const gchar *body_fmt = NULL;
1771 MsgInfo *full_msginfo;
1773 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1774 body_fmt = gettext(prefs_common.fw_quotefmt);
1778 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1780 full_msginfo = procmsg_msginfo_copy(msginfo);
1782 /* use the forward format of folder (if enabled), or the account's one
1783 (if enabled) or fallback to the global forward format, which is always
1784 enabled (even if empty), and use the relevant quotemark */
1785 if (msginfo->folder && msginfo->folder->prefs &&
1786 msginfo->folder->prefs->forward_with_format) {
1787 qmark = msginfo->folder->prefs->forward_quotemark;
1788 body_fmt = msginfo->folder->prefs->forward_body_format;
1790 } else if (account->forward_with_format) {
1791 qmark = account->forward_quotemark;
1792 body_fmt = account->forward_body_format;
1795 qmark = prefs_common.fw_quotemark;
1796 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1797 body_fmt = gettext(prefs_common.fw_quotefmt);
1802 /* empty quotemark is not allowed */
1803 if (qmark == NULL || *qmark == '\0')
1806 compose_quote_fmt(compose, full_msginfo,
1807 body_fmt, qmark, body, FALSE, TRUE,
1808 _("The body of the \"Forward\" template has an error at line %d."));
1809 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1810 quote_fmt_reset_vartable();
1811 compose_attach_parts(compose, msginfo);
1813 procmsg_msginfo_free(full_msginfo);
1815 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1816 gtkaspell_highlight_all(compose->gtkaspell);
1820 SIGNAL_BLOCK(textbuf);
1822 if (account->auto_sig)
1823 compose_insert_sig(compose, FALSE);
1825 compose_wrap_all(compose);
1827 SIGNAL_UNBLOCK(textbuf);
1829 gtk_text_buffer_get_start_iter(textbuf, &iter);
1830 gtk_text_buffer_place_cursor(textbuf, &iter);
1832 gtk_widget_grab_focus(compose->header_last->entry);
1834 if (!no_extedit && prefs_common.auto_exteditor)
1835 compose_exec_ext_editor(compose);
1838 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1839 gchar *folderidentifier;
1841 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1842 folderidentifier = folder_item_get_identifier(msginfo->folder);
1843 compose_set_save_to(compose, folderidentifier);
1844 g_free(folderidentifier);
1847 undo_unblock(compose->undostruct);
1849 compose->modified = FALSE;
1850 compose_set_title(compose);
1852 compose->updating = FALSE;
1853 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1854 SCROLL_TO_CURSOR(compose);
1856 if (compose->deferred_destroy) {
1857 compose_destroy(compose);
1864 #undef INSERT_FW_HEADER
1866 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1869 GtkTextView *textview;
1870 GtkTextBuffer *textbuf;
1874 gboolean single_mail = TRUE;
1876 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1878 if (g_slist_length(msginfo_list) > 1)
1879 single_mail = FALSE;
1881 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1882 if (((MsgInfo *)msginfo->data)->folder == NULL)
1885 /* guess account from first selected message */
1887 !(account = compose_guess_forward_account_from_msginfo
1888 (msginfo_list->data)))
1889 account = cur_account;
1891 cm_return_val_if_fail(account != NULL, NULL);
1893 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1894 if (msginfo->data) {
1895 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1896 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1900 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1901 g_warning("no msginfo_list");
1905 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1907 compose->updating = TRUE;
1909 /* override from name according to folder properties */
1910 if (msginfo_list->data) {
1911 MsgInfo *msginfo = msginfo_list->data;
1913 if (msginfo->folder && msginfo->folder->prefs &&
1914 msginfo->folder->prefs->forward_with_format &&
1915 msginfo->folder->prefs->forward_override_from_format &&
1916 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1921 /* decode \-escape sequences in the internal representation of the quote format */
1922 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1923 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1926 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1927 compose->gtkaspell);
1929 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1931 quote_fmt_scan_string(tmp);
1934 buf = quote_fmt_get_buffer();
1936 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1938 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1939 quote_fmt_reset_vartable();
1945 textview = GTK_TEXT_VIEW(compose->text);
1946 textbuf = gtk_text_view_get_buffer(textview);
1947 compose_create_tags(textview, compose);
1949 undo_block(compose->undostruct);
1950 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1951 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1953 if (!is_file_exist(msgfile))
1954 g_warning("%s: file not exist\n", msgfile);
1956 compose_attach_append(compose, msgfile, msgfile,
1962 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1963 if (info->subject && *info->subject) {
1964 gchar *buf, *buf2, *p;
1966 buf = p = g_strdup(info->subject);
1967 p += subject_get_prefix_length(p);
1968 memmove(buf, p, strlen(p) + 1);
1970 buf2 = g_strdup_printf("Fw: %s", buf);
1971 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1977 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1978 _("Fw: multiple emails"));
1981 SIGNAL_BLOCK(textbuf);
1983 if (account->auto_sig)
1984 compose_insert_sig(compose, FALSE);
1986 compose_wrap_all(compose);
1988 SIGNAL_UNBLOCK(textbuf);
1990 gtk_text_buffer_get_start_iter(textbuf, &iter);
1991 gtk_text_buffer_place_cursor(textbuf, &iter);
1993 gtk_widget_grab_focus(compose->header_last->entry);
1994 undo_unblock(compose->undostruct);
1995 compose->modified = FALSE;
1996 compose_set_title(compose);
1998 compose->updating = FALSE;
1999 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2000 SCROLL_TO_CURSOR(compose);
2002 if (compose->deferred_destroy) {
2003 compose_destroy(compose);
2010 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2012 GtkTextIter start = *iter;
2013 GtkTextIter end_iter;
2014 int start_pos = gtk_text_iter_get_offset(&start);
2016 if (!compose->account->sig_sep)
2019 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2020 start_pos+strlen(compose->account->sig_sep));
2022 /* check sig separator */
2023 str = gtk_text_iter_get_text(&start, &end_iter);
2024 if (!strcmp(str, compose->account->sig_sep)) {
2026 /* check end of line (\n) */
2027 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2028 start_pos+strlen(compose->account->sig_sep));
2029 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2030 start_pos+strlen(compose->account->sig_sep)+1);
2031 tmp = gtk_text_iter_get_text(&start, &end_iter);
2032 if (!strcmp(tmp,"\n")) {
2044 static void compose_colorize_signature(Compose *compose)
2046 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2048 GtkTextIter end_iter;
2049 gtk_text_buffer_get_start_iter(buffer, &iter);
2050 while (gtk_text_iter_forward_line(&iter))
2051 if (compose_is_sig_separator(compose, buffer, &iter)) {
2052 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2053 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2057 #define BLOCK_WRAP() { \
2058 prev_autowrap = compose->autowrap; \
2059 buffer = gtk_text_view_get_buffer( \
2060 GTK_TEXT_VIEW(compose->text)); \
2061 compose->autowrap = FALSE; \
2063 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2064 G_CALLBACK(compose_changed_cb), \
2066 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2067 G_CALLBACK(text_inserted), \
2070 #define UNBLOCK_WRAP() { \
2071 compose->autowrap = prev_autowrap; \
2072 if (compose->autowrap) { \
2073 gint old = compose->draft_timeout_tag; \
2074 compose->draft_timeout_tag = -2; \
2075 compose_wrap_all(compose); \
2076 compose->draft_timeout_tag = old; \
2079 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2080 G_CALLBACK(compose_changed_cb), \
2082 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2083 G_CALLBACK(text_inserted), \
2087 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2089 Compose *compose = NULL;
2090 PrefsAccount *account = NULL;
2091 GtkTextView *textview;
2092 GtkTextBuffer *textbuf;
2096 gchar buf[BUFFSIZE];
2097 gboolean use_signing = FALSE;
2098 gboolean use_encryption = FALSE;
2099 gchar *privacy_system = NULL;
2100 int priority = PRIORITY_NORMAL;
2101 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2102 gboolean autowrap = prefs_common.autowrap;
2103 gboolean autoindent = prefs_common.auto_indent;
2105 cm_return_val_if_fail(msginfo != NULL, NULL);
2106 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2108 if (compose_put_existing_to_front(msginfo)) {
2112 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2113 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2114 gchar queueheader_buf[BUFFSIZE];
2117 /* Select Account from queue headers */
2118 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2119 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2120 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2121 account = account_find_from_id(id);
2123 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2124 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2125 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2126 account = account_find_from_id(id);
2128 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2129 sizeof(queueheader_buf), "NAID:")) {
2130 id = atoi(&queueheader_buf[strlen("NAID:")]);
2131 account = account_find_from_id(id);
2133 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2134 sizeof(queueheader_buf), "MAID:")) {
2135 id = atoi(&queueheader_buf[strlen("MAID:")]);
2136 account = account_find_from_id(id);
2138 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2139 sizeof(queueheader_buf), "S:")) {
2140 account = account_find_from_address(queueheader_buf, FALSE);
2142 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2143 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2144 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2145 use_signing = param;
2148 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2149 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2150 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2151 use_signing = param;
2154 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2155 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2156 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2157 use_encryption = param;
2159 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2160 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2161 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2162 use_encryption = param;
2164 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2165 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2166 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2169 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2170 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2171 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2174 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2175 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2176 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2178 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2179 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2180 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2182 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2183 sizeof(queueheader_buf), "X-Priority: ")) {
2184 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2187 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2188 sizeof(queueheader_buf), "RMID:")) {
2189 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2190 if (tokens[0] && tokens[1] && tokens[2]) {
2191 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2192 if (orig_item != NULL) {
2193 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2198 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2199 sizeof(queueheader_buf), "FMID:")) {
2200 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2201 if (tokens[0] && tokens[1] && tokens[2]) {
2202 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2203 if (orig_item != NULL) {
2204 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2210 account = msginfo->folder->folder->account;
2213 if (!account && prefs_common.reedit_account_autosel) {
2214 gchar from[BUFFSIZE];
2215 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2216 extract_address(from);
2217 account = account_find_from_address(from, FALSE);
2221 account = cur_account;
2223 cm_return_val_if_fail(account != NULL, NULL);
2225 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2227 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2228 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2229 compose->autowrap = autowrap;
2230 compose->replyinfo = replyinfo;
2231 compose->fwdinfo = fwdinfo;
2233 compose->updating = TRUE;
2234 compose->priority = priority;
2236 if (privacy_system != NULL) {
2237 compose->privacy_system = privacy_system;
2238 compose_use_signing(compose, use_signing);
2239 compose_use_encryption(compose, use_encryption);
2240 compose_update_privacy_system_menu_item(compose, FALSE);
2242 activate_privacy_system(compose, account, FALSE);
2245 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2247 compose_extract_original_charset(compose);
2249 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2250 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2251 gchar queueheader_buf[BUFFSIZE];
2253 /* Set message save folder */
2254 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2255 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2256 compose_set_save_to(compose, &queueheader_buf[4]);
2258 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2259 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2261 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2266 if (compose_parse_header(compose, msginfo) < 0) {
2267 compose->updating = FALSE;
2268 compose_destroy(compose);
2271 compose_reedit_set_entry(compose, msginfo);
2273 textview = GTK_TEXT_VIEW(compose->text);
2274 textbuf = gtk_text_view_get_buffer(textview);
2275 compose_create_tags(textview, compose);
2277 mark = gtk_text_buffer_get_insert(textbuf);
2278 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2280 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2281 G_CALLBACK(compose_changed_cb),
2284 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2285 fp = procmime_get_first_encrypted_text_content(msginfo);
2287 compose_force_encryption(compose, account, TRUE, NULL);
2290 fp = procmime_get_first_text_content(msginfo);
2293 g_warning("Can't get text part\n");
2297 gboolean prev_autowrap = compose->autowrap;
2298 GtkTextBuffer *buffer = textbuf;
2300 while (fgets(buf, sizeof(buf), fp) != NULL) {
2302 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2308 compose_attach_parts(compose, msginfo);
2310 compose_colorize_signature(compose);
2312 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2313 G_CALLBACK(compose_changed_cb),
2316 gtk_widget_grab_focus(compose->text);
2318 if (prefs_common.auto_exteditor) {
2319 compose_exec_ext_editor(compose);
2321 compose->modified = FALSE;
2322 compose_set_title(compose);
2324 compose->updating = FALSE;
2325 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2326 SCROLL_TO_CURSOR(compose);
2328 if (compose->deferred_destroy) {
2329 compose_destroy(compose);
2333 compose->sig_str = account_get_signature_str(compose->account);
2338 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2345 cm_return_val_if_fail(msginfo != NULL, NULL);
2348 account = account_get_reply_account(msginfo,
2349 prefs_common.reply_account_autosel);
2350 cm_return_val_if_fail(account != NULL, NULL);
2352 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2354 compose->updating = TRUE;
2356 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2357 compose->replyinfo = NULL;
2358 compose->fwdinfo = NULL;
2360 compose_show_first_last_header(compose, TRUE);
2362 gtk_widget_grab_focus(compose->header_last->entry);
2364 filename = procmsg_get_message_file(msginfo);
2366 if (filename == NULL) {
2367 compose->updating = FALSE;
2368 compose_destroy(compose);
2373 compose->redirect_filename = filename;
2375 /* Set save folder */
2376 item = msginfo->folder;
2377 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2378 gchar *folderidentifier;
2380 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2381 folderidentifier = folder_item_get_identifier(item);
2382 compose_set_save_to(compose, folderidentifier);
2383 g_free(folderidentifier);
2386 compose_attach_parts(compose, msginfo);
2388 if (msginfo->subject)
2389 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2391 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2393 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2394 _("The body of the \"Redirect\" template has an error at line %d."));
2395 quote_fmt_reset_vartable();
2396 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2398 compose_colorize_signature(compose);
2401 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2402 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2403 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2405 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2406 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2407 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2408 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2409 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2410 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2411 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2412 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2414 if (compose->toolbar->draft_btn)
2415 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2416 if (compose->toolbar->insert_btn)
2417 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2418 if (compose->toolbar->attach_btn)
2419 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2420 if (compose->toolbar->sig_btn)
2421 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2422 if (compose->toolbar->exteditor_btn)
2423 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2424 if (compose->toolbar->linewrap_current_btn)
2425 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2426 if (compose->toolbar->linewrap_all_btn)
2427 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2429 compose->modified = FALSE;
2430 compose_set_title(compose);
2431 compose->updating = FALSE;
2432 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2433 SCROLL_TO_CURSOR(compose);
2435 if (compose->deferred_destroy) {
2436 compose_destroy(compose);
2443 GList *compose_get_compose_list(void)
2445 return compose_list;
2448 void compose_entry_append(Compose *compose, const gchar *address,
2449 ComposeEntryType type, ComposePrefType pref_type)
2451 const gchar *header;
2453 gboolean in_quote = FALSE;
2454 if (!address || *address == '\0') return;
2461 header = N_("Bcc:");
2463 case COMPOSE_REPLYTO:
2464 header = N_("Reply-To:");
2466 case COMPOSE_NEWSGROUPS:
2467 header = N_("Newsgroups:");
2469 case COMPOSE_FOLLOWUPTO:
2470 header = N_( "Followup-To:");
2477 header = prefs_common_translated_header_name(header);
2479 cur = begin = (gchar *)address;
2481 /* we separate the line by commas, but not if we're inside a quoted
2483 while (*cur != '\0') {
2485 in_quote = !in_quote;
2486 if (*cur == ',' && !in_quote) {
2487 gchar *tmp = g_strdup(begin);
2489 tmp[cur-begin]='\0';
2492 while (*tmp == ' ' || *tmp == '\t')
2494 compose_add_header_entry(compose, header, tmp, pref_type);
2501 gchar *tmp = g_strdup(begin);
2503 tmp[cur-begin]='\0';
2506 while (*tmp == ' ' || *tmp == '\t')
2508 compose_add_header_entry(compose, header, tmp, pref_type);
2513 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2515 static GdkColor yellow;
2516 static GdkColor black;
2517 static gboolean yellow_initialised = FALSE;
2521 if (!yellow_initialised) {
2522 gdk_color_parse("#f5f6be", &yellow);
2523 gdk_color_parse("#000000", &black);
2524 yellow_initialised = gdk_colormap_alloc_color(
2525 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2526 yellow_initialised &= gdk_colormap_alloc_color(
2527 gdk_colormap_get_system(), &black, FALSE, TRUE);
2530 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2531 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2532 if (gtk_entry_get_text(entry) &&
2533 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2534 if (yellow_initialised) {
2535 gtk_widget_modify_base(
2536 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2537 GTK_STATE_NORMAL, &yellow);
2538 gtk_widget_modify_text(
2539 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2540 GTK_STATE_NORMAL, &black);
2546 void compose_toolbar_cb(gint action, gpointer data)
2548 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2549 Compose *compose = (Compose*)toolbar_item->parent;
2551 cm_return_if_fail(compose != NULL);
2555 compose_send_cb(NULL, compose);
2558 compose_send_later_cb(NULL, compose);
2561 compose_draft(compose, COMPOSE_QUIT_EDITING);
2564 compose_insert_file_cb(NULL, compose);
2567 compose_attach_cb(NULL, compose);
2570 compose_insert_sig(compose, FALSE);
2573 compose_ext_editor_cb(NULL, compose);
2575 case A_LINEWRAP_CURRENT:
2576 compose_beautify_paragraph(compose, NULL, TRUE);
2578 case A_LINEWRAP_ALL:
2579 compose_wrap_all_full(compose, TRUE);
2582 compose_address_cb(NULL, compose);
2585 case A_CHECK_SPELLING:
2586 compose_check_all(NULL, compose);
2594 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2599 gchar *subject = NULL;
2603 gchar **attach = NULL;
2604 MailField mfield = NO_FIELD_PRESENT;
2606 /* get mailto parts but skip from */
2607 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2610 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2611 mfield = TO_FIELD_PRESENT;
2614 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2616 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2618 if (!g_utf8_validate (subject, -1, NULL)) {
2619 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2620 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2623 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2625 mfield = SUBJECT_FIELD_PRESENT;
2628 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2629 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2632 gboolean prev_autowrap = compose->autowrap;
2634 compose->autowrap = FALSE;
2636 mark = gtk_text_buffer_get_insert(buffer);
2637 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2639 if (!g_utf8_validate (body, -1, NULL)) {
2640 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2641 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2644 gtk_text_buffer_insert(buffer, &iter, body, -1);
2646 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2648 compose->autowrap = prev_autowrap;
2649 if (compose->autowrap)
2650 compose_wrap_all(compose);
2651 mfield = BODY_FIELD_PRESENT;
2655 gint i = 0, att = 0;
2656 gchar *warn_files = NULL;
2657 while (attach[i] != NULL) {
2658 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2659 if (utf8_filename) {
2660 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2661 gchar *tmp = g_strdup_printf("%s%s\n",
2662 warn_files?warn_files:"",
2668 g_free(utf8_filename);
2670 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2675 alertpanel_notice(ngettext(
2676 "The following file has been attached: \n%s",
2677 "The following files have been attached: \n%s", att), warn_files);
2691 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2693 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2694 {"Cc:", NULL, TRUE},
2695 {"References:", NULL, FALSE},
2696 {"Bcc:", NULL, TRUE},
2697 {"Newsgroups:", NULL, TRUE},
2698 {"Followup-To:", NULL, TRUE},
2699 {"List-Post:", NULL, FALSE},
2700 {"X-Priority:", NULL, FALSE},
2701 {NULL, NULL, FALSE}};
2717 cm_return_val_if_fail(msginfo != NULL, -1);
2719 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2720 procheader_get_header_fields(fp, hentry);
2723 if (hentry[H_REPLY_TO].body != NULL) {
2724 if (hentry[H_REPLY_TO].body[0] != '\0') {
2726 conv_unmime_header(hentry[H_REPLY_TO].body,
2729 g_free(hentry[H_REPLY_TO].body);
2730 hentry[H_REPLY_TO].body = NULL;
2732 if (hentry[H_CC].body != NULL) {
2733 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2734 g_free(hentry[H_CC].body);
2735 hentry[H_CC].body = NULL;
2737 if (hentry[H_REFERENCES].body != NULL) {
2738 if (compose->mode == COMPOSE_REEDIT)
2739 compose->references = hentry[H_REFERENCES].body;
2741 compose->references = compose_parse_references
2742 (hentry[H_REFERENCES].body, msginfo->msgid);
2743 g_free(hentry[H_REFERENCES].body);
2745 hentry[H_REFERENCES].body = NULL;
2747 if (hentry[H_BCC].body != NULL) {
2748 if (compose->mode == COMPOSE_REEDIT)
2750 conv_unmime_header(hentry[H_BCC].body, NULL);
2751 g_free(hentry[H_BCC].body);
2752 hentry[H_BCC].body = NULL;
2754 if (hentry[H_NEWSGROUPS].body != NULL) {
2755 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2756 hentry[H_NEWSGROUPS].body = NULL;
2758 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2759 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2760 compose->followup_to =
2761 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2764 g_free(hentry[H_FOLLOWUP_TO].body);
2765 hentry[H_FOLLOWUP_TO].body = NULL;
2767 if (hentry[H_LIST_POST].body != NULL) {
2768 gchar *to = NULL, *start = NULL;
2770 extract_address(hentry[H_LIST_POST].body);
2771 if (hentry[H_LIST_POST].body[0] != '\0') {
2772 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2774 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2775 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2778 g_free(compose->ml_post);
2779 compose->ml_post = to;
2782 g_free(hentry[H_LIST_POST].body);
2783 hentry[H_LIST_POST].body = NULL;
2786 /* CLAWS - X-Priority */
2787 if (compose->mode == COMPOSE_REEDIT)
2788 if (hentry[H_X_PRIORITY].body != NULL) {
2791 priority = atoi(hentry[H_X_PRIORITY].body);
2792 g_free(hentry[H_X_PRIORITY].body);
2794 hentry[H_X_PRIORITY].body = NULL;
2796 if (priority < PRIORITY_HIGHEST ||
2797 priority > PRIORITY_LOWEST)
2798 priority = PRIORITY_NORMAL;
2800 compose->priority = priority;
2803 if (compose->mode == COMPOSE_REEDIT) {
2804 if (msginfo->inreplyto && *msginfo->inreplyto)
2805 compose->inreplyto = g_strdup(msginfo->inreplyto);
2809 if (msginfo->msgid && *msginfo->msgid)
2810 compose->inreplyto = g_strdup(msginfo->msgid);
2812 if (!compose->references) {
2813 if (msginfo->msgid && *msginfo->msgid) {
2814 if (msginfo->inreplyto && *msginfo->inreplyto)
2815 compose->references =
2816 g_strdup_printf("<%s>\n\t<%s>",
2820 compose->references =
2821 g_strconcat("<", msginfo->msgid, ">",
2823 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2824 compose->references =
2825 g_strconcat("<", msginfo->inreplyto, ">",
2833 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2835 GSList *ref_id_list, *cur;
2839 ref_id_list = references_list_append(NULL, ref);
2840 if (!ref_id_list) return NULL;
2841 if (msgid && *msgid)
2842 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2847 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2848 /* "<" + Message-ID + ">" + CR+LF+TAB */
2849 len += strlen((gchar *)cur->data) + 5;
2851 if (len > MAX_REFERENCES_LEN) {
2852 /* remove second message-ID */
2853 if (ref_id_list && ref_id_list->next &&
2854 ref_id_list->next->next) {
2855 g_free(ref_id_list->next->data);
2856 ref_id_list = g_slist_remove
2857 (ref_id_list, ref_id_list->next->data);
2859 slist_free_strings(ref_id_list);
2860 g_slist_free(ref_id_list);
2867 new_ref = g_string_new("");
2868 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2869 if (new_ref->len > 0)
2870 g_string_append(new_ref, "\n\t");
2871 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2874 slist_free_strings(ref_id_list);
2875 g_slist_free(ref_id_list);
2877 new_ref_str = new_ref->str;
2878 g_string_free(new_ref, FALSE);
2883 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2884 const gchar *fmt, const gchar *qmark,
2885 const gchar *body, gboolean rewrap,
2886 gboolean need_unescape,
2887 const gchar *err_msg)
2889 MsgInfo* dummyinfo = NULL;
2890 gchar *quote_str = NULL;
2892 gboolean prev_autowrap;
2893 const gchar *trimmed_body = body;
2894 gint cursor_pos = -1;
2895 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2896 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2901 SIGNAL_BLOCK(buffer);
2904 dummyinfo = compose_msginfo_new_from_compose(compose);
2905 msginfo = dummyinfo;
2908 if (qmark != NULL) {
2910 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2911 compose->gtkaspell);
2913 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2915 quote_fmt_scan_string(qmark);
2918 buf = quote_fmt_get_buffer();
2920 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
2922 Xstrdup_a(quote_str, buf, goto error)
2925 if (fmt && *fmt != '\0') {
2928 while (*trimmed_body == '\n')
2932 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2933 compose->gtkaspell);
2935 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2937 if (need_unescape) {
2940 /* decode \-escape sequences in the internal representation of the quote format */
2941 tmp = malloc(strlen(fmt)+1);
2942 pref_get_unescaped_pref(tmp, fmt);
2943 quote_fmt_scan_string(tmp);
2947 quote_fmt_scan_string(fmt);
2951 buf = quote_fmt_get_buffer();
2953 gint line = quote_fmt_get_line();
2954 alertpanel_error(err_msg, line);
2960 prev_autowrap = compose->autowrap;
2961 compose->autowrap = FALSE;
2963 mark = gtk_text_buffer_get_insert(buffer);
2964 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2965 if (g_utf8_validate(buf, -1, NULL)) {
2966 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2968 gchar *tmpout = NULL;
2969 tmpout = conv_codeset_strdup
2970 (buf, conv_get_locale_charset_str_no_utf8(),
2972 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2974 tmpout = g_malloc(strlen(buf)*2+1);
2975 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2977 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2981 cursor_pos = quote_fmt_get_cursor_pos();
2982 if (cursor_pos == -1)
2983 cursor_pos = gtk_text_iter_get_offset(&iter);
2984 compose->set_cursor_pos = cursor_pos;
2986 gtk_text_buffer_get_start_iter(buffer, &iter);
2987 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2988 gtk_text_buffer_place_cursor(buffer, &iter);
2990 compose->autowrap = prev_autowrap;
2991 if (compose->autowrap && rewrap)
2992 compose_wrap_all(compose);
2999 SIGNAL_UNBLOCK(buffer);
3001 procmsg_msginfo_free( dummyinfo );
3006 /* if ml_post is of type addr@host and from is of type
3007 * addr-anything@host, return TRUE
3009 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3011 gchar *left_ml = NULL;
3012 gchar *right_ml = NULL;
3013 gchar *left_from = NULL;
3014 gchar *right_from = NULL;
3015 gboolean result = FALSE;
3017 if (!ml_post || !from)
3020 left_ml = g_strdup(ml_post);
3021 if (strstr(left_ml, "@")) {
3022 right_ml = strstr(left_ml, "@")+1;
3023 *(strstr(left_ml, "@")) = '\0';
3026 left_from = g_strdup(from);
3027 if (strstr(left_from, "@")) {
3028 right_from = strstr(left_from, "@")+1;
3029 *(strstr(left_from, "@")) = '\0';
3032 if (left_ml && left_from && right_ml && right_from
3033 && !strncmp(left_from, left_ml, strlen(left_ml))
3034 && !strcmp(right_from, right_ml)) {
3043 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3044 gboolean to_all, gboolean to_ml,
3046 gboolean followup_and_reply_to)
3048 GSList *cc_list = NULL;
3051 gchar *replyto = NULL;
3053 gboolean reply_to_ml = FALSE;
3054 gboolean default_reply_to = FALSE;
3056 cm_return_if_fail(compose->account != NULL);
3057 cm_return_if_fail(msginfo != NULL);
3059 reply_to_ml = to_ml && compose->ml_post;
3061 default_reply_to = msginfo->folder &&
3062 msginfo->folder->prefs->enable_default_reply_to;
3064 if (compose->account->protocol != A_NNTP) {
3065 if (msginfo && msginfo->folder && msginfo->folder->prefs) {
3066 if (msginfo->folder->prefs->enable_default_replyto) {
3067 compose_entry_append(compose, msginfo->folder->prefs->default_replyto,
3068 COMPOSE_REPLYTO, PREF_FOLDER);
3070 if (msginfo->folder->prefs->enable_default_bcc) {
3071 compose_entry_append(compose, msginfo->folder->prefs->default_bcc,
3072 COMPOSE_BCC, PREF_FOLDER);
3074 if (msginfo->folder->prefs->enable_default_cc) {
3075 compose_entry_append(compose, msginfo->folder->prefs->default_cc,
3076 COMPOSE_CC, PREF_FOLDER);
3079 if (reply_to_ml && !default_reply_to) {
3081 gboolean is_subscr = is_subscription(compose->ml_post,
3084 /* normal answer to ml post with a reply-to */
3085 compose_entry_append(compose,
3087 COMPOSE_TO, PREF_ML);
3088 if (compose->replyto)
3089 compose_entry_append(compose,
3091 COMPOSE_CC, PREF_ML);
3093 /* answer to subscription confirmation */
3094 if (compose->replyto)
3095 compose_entry_append(compose,
3097 COMPOSE_TO, PREF_ML);
3098 else if (msginfo->from)
3099 compose_entry_append(compose,
3101 COMPOSE_TO, PREF_ML);
3104 else if (!(to_all || to_sender) && default_reply_to) {
3105 compose_entry_append(compose,
3106 msginfo->folder->prefs->default_reply_to,
3107 COMPOSE_TO, PREF_FOLDER);
3108 compose_entry_mark_default_to(compose,
3109 msginfo->folder->prefs->default_reply_to);
3114 Xstrdup_a(tmp1, msginfo->from, return);
3115 extract_address(tmp1);
3116 if (to_all || to_sender ||
3117 !account_find_from_address(tmp1, FALSE))
3118 compose_entry_append(compose,
3119 (compose->replyto && !to_sender)
3120 ? compose->replyto :
3121 msginfo->from ? msginfo->from : "",
3122 COMPOSE_TO, PREF_NONE);
3123 else if (!to_all && !to_sender) {
3124 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3125 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3126 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3127 if (compose->replyto) {
3128 compose_entry_append(compose,
3130 COMPOSE_TO, PREF_NONE);
3132 compose_entry_append(compose,
3133 msginfo->from ? msginfo->from : "",
3134 COMPOSE_TO, PREF_NONE);
3137 /* replying to own mail, use original recp */
3138 compose_entry_append(compose,
3139 msginfo->to ? msginfo->to : "",
3140 COMPOSE_TO, PREF_NONE);
3141 compose_entry_append(compose,
3142 msginfo->cc ? msginfo->cc : "",
3143 COMPOSE_CC, PREF_NONE);
3148 if (to_sender || (compose->followup_to &&
3149 !strncmp(compose->followup_to, "poster", 6)))
3150 compose_entry_append
3152 (compose->replyto ? compose->replyto :
3153 msginfo->from ? msginfo->from : ""),
3154 COMPOSE_TO, PREF_NONE);
3156 else if (followup_and_reply_to || to_all) {
3157 compose_entry_append
3159 (compose->replyto ? compose->replyto :
3160 msginfo->from ? msginfo->from : ""),
3161 COMPOSE_TO, PREF_NONE);
3163 compose_entry_append
3165 compose->followup_to ? compose->followup_to :
3166 compose->newsgroups ? compose->newsgroups : "",
3167 COMPOSE_NEWSGROUPS, PREF_NONE);
3170 compose_entry_append
3172 compose->followup_to ? compose->followup_to :
3173 compose->newsgroups ? compose->newsgroups : "",
3174 COMPOSE_NEWSGROUPS, PREF_NONE);
3177 if (msginfo->subject && *msginfo->subject) {
3181 buf = p = g_strdup(msginfo->subject);
3182 p += subject_get_prefix_length(p);
3183 memmove(buf, p, strlen(p) + 1);
3185 buf2 = g_strdup_printf("Re: %s", buf);
3186 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3191 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3193 if (to_ml && compose->ml_post) return;
3194 if (!to_all || compose->account->protocol == A_NNTP) return;
3196 if (compose->replyto) {
3197 Xstrdup_a(replyto, compose->replyto, return);
3198 extract_address(replyto);
3200 if (msginfo->from) {
3201 Xstrdup_a(from, msginfo->from, return);
3202 extract_address(from);
3205 if (replyto && from)
3206 cc_list = address_list_append_with_comments(cc_list, from);
3207 if (to_all && msginfo->folder &&
3208 msginfo->folder->prefs->enable_default_reply_to)
3209 cc_list = address_list_append_with_comments(cc_list,
3210 msginfo->folder->prefs->default_reply_to);
3211 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3212 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3215 for (cur = cc_list; cur != NULL; cur = cur->next)
3216 compose_entry_append(compose, (gchar *)cur->data,
3217 COMPOSE_CC, PREF_NONE);
3218 slist_free_strings(cc_list);
3219 g_slist_free(cc_list);
3223 #define SET_ENTRY(entry, str) \
3226 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3229 #define SET_ADDRESS(type, str) \
3232 compose_entry_append(compose, str, type, PREF_NONE); \
3235 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3237 cm_return_if_fail(msginfo != NULL);
3239 SET_ENTRY(subject_entry, msginfo->subject);
3240 SET_ENTRY(from_name, msginfo->from);
3241 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3242 SET_ADDRESS(COMPOSE_CC, compose->cc);
3243 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3244 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3245 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3246 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3248 compose_update_priority_menu_item(compose);
3249 compose_update_privacy_system_menu_item(compose, FALSE);
3250 compose_show_first_last_header(compose, TRUE);
3256 static void compose_insert_sig(Compose *compose, gboolean replace)
3258 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3259 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3261 GtkTextIter iter, iter_end;
3262 gint cur_pos, ins_pos;
3263 gboolean prev_autowrap;
3264 gboolean found = FALSE;
3265 gboolean exists = FALSE;
3267 cm_return_if_fail(compose->account != NULL);
3271 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3272 G_CALLBACK(compose_changed_cb),
3275 mark = gtk_text_buffer_get_insert(buffer);
3276 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3277 cur_pos = gtk_text_iter_get_offset (&iter);
3280 gtk_text_buffer_get_end_iter(buffer, &iter);
3282 exists = (compose->sig_str != NULL);
3285 GtkTextIter first_iter, start_iter, end_iter;
3287 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3289 if (!exists || compose->sig_str[0] == '\0')
3292 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3293 compose->signature_tag);
3296 /* include previous \n\n */
3297 gtk_text_iter_backward_chars(&first_iter, 1);
3298 start_iter = first_iter;
3299 end_iter = first_iter;
3301 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3302 compose->signature_tag);
3303 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3304 compose->signature_tag);
3306 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3312 g_free(compose->sig_str);
3313 compose->sig_str = account_get_signature_str(compose->account);
3315 cur_pos = gtk_text_iter_get_offset(&iter);
3317 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3318 g_free(compose->sig_str);
3319 compose->sig_str = NULL;
3321 if (compose->sig_inserted == FALSE)
3322 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3323 compose->sig_inserted = TRUE;
3325 cur_pos = gtk_text_iter_get_offset(&iter);
3326 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3328 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3329 gtk_text_iter_forward_chars(&iter, 1);
3330 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3331 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3333 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3334 cur_pos = gtk_text_buffer_get_char_count (buffer);
3337 /* put the cursor where it should be
3338 * either where the quote_fmt says, either where it was */
3339 if (compose->set_cursor_pos < 0)
3340 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3342 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3343 compose->set_cursor_pos);
3345 compose->set_cursor_pos = -1;
3346 gtk_text_buffer_place_cursor(buffer, &iter);
3347 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3348 G_CALLBACK(compose_changed_cb),
3354 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3357 GtkTextBuffer *buffer;
3360 const gchar *cur_encoding;
3361 gchar buf[BUFFSIZE];
3364 gboolean prev_autowrap;
3365 gboolean badtxt = FALSE;
3366 struct stat file_stat;
3369 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3371 /* get the size of the file we are about to insert */
3372 ret = g_stat(file, &file_stat);
3374 gchar *shortfile = g_path_get_basename(file);
3375 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3377 return COMPOSE_INSERT_NO_FILE;
3378 } else if (prefs_common.warn_large_insert == TRUE) {
3380 /* ask user for confirmation if the file is large */
3381 if (prefs_common.warn_large_insert_size < 0 ||
3382 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3386 msg = g_strdup_printf(_("You are about to insert a file of %s "
3387 "in the message body. Are you sure you want to do that?"),
3388 to_human_readable(file_stat.st_size));
3389 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3390 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3393 /* do we ask for confirmation next time? */
3394 if (aval & G_ALERTDISABLE) {
3395 /* no confirmation next time, disable feature in preferences */
3396 aval &= ~G_ALERTDISABLE;
3397 prefs_common.warn_large_insert = FALSE;
3400 /* abort file insertion if user canceled action */
3401 if (aval != G_ALERTALTERNATE) {
3402 return COMPOSE_INSERT_NO_FILE;
3408 if ((fp = g_fopen(file, "rb")) == NULL) {
3409 FILE_OP_ERROR(file, "fopen");
3410 return COMPOSE_INSERT_READ_ERROR;
3413 prev_autowrap = compose->autowrap;
3414 compose->autowrap = FALSE;
3416 text = GTK_TEXT_VIEW(compose->text);
3417 buffer = gtk_text_view_get_buffer(text);
3418 mark = gtk_text_buffer_get_insert(buffer);
3419 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3421 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3422 G_CALLBACK(text_inserted),
3425 cur_encoding = conv_get_locale_charset_str_no_utf8();
3427 while (fgets(buf, sizeof(buf), fp) != NULL) {
3430 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3431 str = g_strdup(buf);
3433 str = conv_codeset_strdup
3434 (buf, cur_encoding, CS_INTERNAL);
3437 /* strip <CR> if DOS/Windows file,
3438 replace <CR> with <LF> if Macintosh file. */
3441 if (len > 0 && str[len - 1] != '\n') {
3443 if (str[len] == '\r') str[len] = '\n';
3446 gtk_text_buffer_insert(buffer, &iter, str, -1);
3450 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3451 G_CALLBACK(text_inserted),
3453 compose->autowrap = prev_autowrap;
3454 if (compose->autowrap)
3455 compose_wrap_all(compose);
3460 return COMPOSE_INSERT_INVALID_CHARACTER;
3462 return COMPOSE_INSERT_SUCCESS;
3465 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3466 const gchar *filename,
3467 const gchar *content_type)
3475 GtkListStore *store;
3477 gboolean has_binary = FALSE;
3479 if (!is_file_exist(file)) {
3480 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3481 gboolean result = FALSE;
3482 if (file_from_uri && is_file_exist(file_from_uri)) {
3483 result = compose_attach_append(
3484 compose, file_from_uri,
3488 g_free(file_from_uri);
3491 alertpanel_error("File %s doesn't exist\n", filename);
3494 if ((size = get_file_size(file)) < 0) {
3495 alertpanel_error("Can't get file size of %s\n", filename);
3499 alertpanel_error(_("File %s is empty."), filename);
3502 if ((fp = g_fopen(file, "rb")) == NULL) {
3503 alertpanel_error(_("Can't read %s."), filename);
3508 ainfo = g_new0(AttachInfo, 1);
3509 auto_ainfo = g_auto_pointer_new_with_free
3510 (ainfo, (GFreeFunc) compose_attach_info_free);
3511 ainfo->file = g_strdup(file);
3514 ainfo->content_type = g_strdup(content_type);
3515 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3517 MsgFlags flags = {0, 0};
3519 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3520 ainfo->encoding = ENC_7BIT;
3522 ainfo->encoding = ENC_8BIT;
3524 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3525 if (msginfo && msginfo->subject)
3526 name = g_strdup(msginfo->subject);
3528 name = g_path_get_basename(filename ? filename : file);
3530 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3532 procmsg_msginfo_free(msginfo);
3534 if (!g_ascii_strncasecmp(content_type, "text", 4))
3535 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3537 ainfo->encoding = ENC_BASE64;
3538 name = g_path_get_basename(filename ? filename : file);
3539 ainfo->name = g_strdup(name);
3543 ainfo->content_type = procmime_get_mime_type(file);
3544 if (!ainfo->content_type) {
3545 ainfo->content_type =
3546 g_strdup("application/octet-stream");
3547 ainfo->encoding = ENC_BASE64;
3548 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3550 procmime_get_encoding_for_text_file(file, &has_binary);
3552 ainfo->encoding = ENC_BASE64;
3553 name = g_path_get_basename(filename ? filename : file);
3554 ainfo->name = g_strdup(name);
3558 if (ainfo->name != NULL
3559 && !strcmp(ainfo->name, ".")) {
3560 g_free(ainfo->name);
3564 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3565 g_free(ainfo->content_type);
3566 ainfo->content_type = g_strdup("application/octet-stream");
3569 ainfo->size = (goffset)size;
3570 size_text = to_human_readable((goffset)size);
3572 store = GTK_LIST_STORE(gtk_tree_view_get_model
3573 (GTK_TREE_VIEW(compose->attach_clist)));
3575 gtk_list_store_append(store, &iter);
3576 gtk_list_store_set(store, &iter,
3577 COL_MIMETYPE, ainfo->content_type,
3578 COL_SIZE, size_text,
3579 COL_NAME, ainfo->name,
3581 COL_AUTODATA, auto_ainfo,
3584 g_auto_pointer_free(auto_ainfo);
3585 compose_attach_update_label(compose);
3589 static void compose_use_signing(Compose *compose, gboolean use_signing)
3591 compose->use_signing = use_signing;
3592 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3595 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3597 compose->use_encryption = use_encryption;
3598 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3601 #define NEXT_PART_NOT_CHILD(info) \
3603 node = info->node; \
3604 while (node->children) \
3605 node = g_node_last_child(node); \
3606 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3609 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3613 MimeInfo *firsttext = NULL;
3614 MimeInfo *encrypted = NULL;
3617 const gchar *partname = NULL;
3619 mimeinfo = procmime_scan_message(msginfo);
3620 if (!mimeinfo) return;
3622 if (mimeinfo->node->children == NULL) {
3623 procmime_mimeinfo_free_all(mimeinfo);
3627 /* find first content part */
3628 child = (MimeInfo *) mimeinfo->node->children->data;
3629 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3630 child = (MimeInfo *)child->node->children->data;
3633 if (child->type == MIMETYPE_TEXT) {
3635 debug_print("First text part found\n");
3636 } else if (compose->mode == COMPOSE_REEDIT &&
3637 child->type == MIMETYPE_APPLICATION &&
3638 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3639 encrypted = (MimeInfo *)child->node->parent->data;
3642 child = (MimeInfo *) mimeinfo->node->children->data;
3643 while (child != NULL) {
3646 if (child == encrypted) {
3647 /* skip this part of tree */
3648 NEXT_PART_NOT_CHILD(child);
3652 if (child->type == MIMETYPE_MULTIPART) {
3653 /* get the actual content */
3654 child = procmime_mimeinfo_next(child);
3658 if (child == firsttext) {
3659 child = procmime_mimeinfo_next(child);
3663 outfile = procmime_get_tmp_file_name(child);
3664 if ((err = procmime_get_part(outfile, child)) < 0)
3665 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3667 gchar *content_type;
3669 content_type = procmime_get_content_type_str(child->type, child->subtype);
3671 /* if we meet a pgp signature, we don't attach it, but
3672 * we force signing. */
3673 if ((strcmp(content_type, "application/pgp-signature") &&
3674 strcmp(content_type, "application/pkcs7-signature") &&
3675 strcmp(content_type, "application/x-pkcs7-signature"))
3676 || compose->mode == COMPOSE_REDIRECT) {
3677 partname = procmime_mimeinfo_get_parameter(child, "filename");
3678 if (partname == NULL)
3679 partname = procmime_mimeinfo_get_parameter(child, "name");
3680 if (partname == NULL)
3682 compose_attach_append(compose, outfile,
3683 partname, content_type);
3685 compose_force_signing(compose, compose->account, NULL);
3687 g_free(content_type);
3690 NEXT_PART_NOT_CHILD(child);
3692 procmime_mimeinfo_free_all(mimeinfo);
3695 #undef NEXT_PART_NOT_CHILD
3700 WAIT_FOR_INDENT_CHAR,
3701 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3704 /* return indent length, we allow:
3705 indent characters followed by indent characters or spaces/tabs,
3706 alphabets and numbers immediately followed by indent characters,
3707 and the repeating sequences of the above
3708 If quote ends with multiple spaces, only the first one is included. */
3709 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3710 const GtkTextIter *start, gint *len)
3712 GtkTextIter iter = *start;
3716 IndentState state = WAIT_FOR_INDENT_CHAR;
3719 gint alnum_count = 0;
3720 gint space_count = 0;
3723 if (prefs_common.quote_chars == NULL) {
3727 while (!gtk_text_iter_ends_line(&iter)) {
3728 wc = gtk_text_iter_get_char(&iter);
3729 if (g_unichar_iswide(wc))
3731 clen = g_unichar_to_utf8(wc, ch);
3735 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3736 is_space = g_unichar_isspace(wc);
3738 if (state == WAIT_FOR_INDENT_CHAR) {
3739 if (!is_indent && !g_unichar_isalnum(wc))
3742 quote_len += alnum_count + space_count + 1;
3743 alnum_count = space_count = 0;
3744 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3747 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3748 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3752 else if (is_indent) {
3753 quote_len += alnum_count + space_count + 1;
3754 alnum_count = space_count = 0;
3757 state = WAIT_FOR_INDENT_CHAR;
3761 gtk_text_iter_forward_char(&iter);
3764 if (quote_len > 0 && space_count > 0)
3770 if (quote_len > 0) {
3772 gtk_text_iter_forward_chars(&iter, quote_len);
3773 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3779 /* return >0 if the line is itemized */
3780 static int compose_itemized_length(GtkTextBuffer *buffer,
3781 const GtkTextIter *start)
3783 GtkTextIter iter = *start;
3788 if (gtk_text_iter_ends_line(&iter))
3793 wc = gtk_text_iter_get_char(&iter);
3794 if (!g_unichar_isspace(wc))
3796 gtk_text_iter_forward_char(&iter);
3797 if (gtk_text_iter_ends_line(&iter))
3801 clen = g_unichar_to_utf8(wc, ch);
3805 if (!strchr("*-+", ch[0]))
3808 gtk_text_iter_forward_char(&iter);
3809 if (gtk_text_iter_ends_line(&iter))
3811 wc = gtk_text_iter_get_char(&iter);
3812 if (g_unichar_isspace(wc)) {
3818 /* return the string at the start of the itemization */
3819 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3820 const GtkTextIter *start)
3822 GtkTextIter iter = *start;
3825 GString *item_chars = g_string_new("");
3828 if (gtk_text_iter_ends_line(&iter))
3833 wc = gtk_text_iter_get_char(&iter);
3834 if (!g_unichar_isspace(wc))
3836 gtk_text_iter_forward_char(&iter);
3837 if (gtk_text_iter_ends_line(&iter))
3839 g_string_append_unichar(item_chars, wc);
3842 str = item_chars->str;
3843 g_string_free(item_chars, FALSE);
3847 /* return the number of spaces at a line's start */
3848 static int compose_left_offset_length(GtkTextBuffer *buffer,
3849 const GtkTextIter *start)
3851 GtkTextIter iter = *start;
3854 if (gtk_text_iter_ends_line(&iter))
3858 wc = gtk_text_iter_get_char(&iter);
3859 if (!g_unichar_isspace(wc))
3862 gtk_text_iter_forward_char(&iter);
3863 if (gtk_text_iter_ends_line(&iter))
3867 gtk_text_iter_forward_char(&iter);
3868 if (gtk_text_iter_ends_line(&iter))
3873 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3874 const GtkTextIter *start,
3875 GtkTextIter *break_pos,
3879 GtkTextIter iter = *start, line_end = *start;
3880 PangoLogAttr *attrs;
3887 gboolean can_break = FALSE;
3888 gboolean do_break = FALSE;
3889 gboolean was_white = FALSE;
3890 gboolean prev_dont_break = FALSE;
3892 gtk_text_iter_forward_to_line_end(&line_end);
3893 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3894 len = g_utf8_strlen(str, -1);
3898 g_warning("compose_get_line_break_pos: len = 0!\n");
3902 /* g_print("breaking line: %d: %s (len = %d)\n",
3903 gtk_text_iter_get_line(&iter), str, len); */
3905 attrs = g_new(PangoLogAttr, len + 1);
3907 pango_default_break(str, -1, NULL, attrs, len + 1);
3911 /* skip quote and leading spaces */
3912 for (i = 0; *p != '\0' && i < len; i++) {
3915 wc = g_utf8_get_char(p);
3916 if (i >= quote_len && !g_unichar_isspace(wc))
3918 if (g_unichar_iswide(wc))
3920 else if (*p == '\t')
3924 p = g_utf8_next_char(p);
3927 for (; *p != '\0' && i < len; i++) {
3928 PangoLogAttr *attr = attrs + i;
3932 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3935 was_white = attr->is_white;
3937 /* don't wrap URI */
3938 if ((uri_len = get_uri_len(p)) > 0) {
3940 if (pos > 0 && col > max_col) {
3950 wc = g_utf8_get_char(p);
3951 if (g_unichar_iswide(wc)) {
3953 if (prev_dont_break && can_break && attr->is_line_break)
3955 } else if (*p == '\t')
3959 if (pos > 0 && col > max_col) {
3964 if (*p == '-' || *p == '/')
3965 prev_dont_break = TRUE;
3967 prev_dont_break = FALSE;
3969 p = g_utf8_next_char(p);
3973 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3978 *break_pos = *start;
3979 gtk_text_iter_set_line_offset(break_pos, pos);
3984 static gboolean compose_join_next_line(Compose *compose,
3985 GtkTextBuffer *buffer,
3987 const gchar *quote_str)
3989 GtkTextIter iter_ = *iter, cur, prev, next, end;
3990 PangoLogAttr attrs[3];
3992 gchar *next_quote_str;
3995 gboolean keep_cursor = FALSE;
3997 if (!gtk_text_iter_forward_line(&iter_) ||
3998 gtk_text_iter_ends_line(&iter_)) {
4001 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4003 if ((quote_str || next_quote_str) &&
4004 strcmp2(quote_str, next_quote_str) != 0) {
4005 g_free(next_quote_str);
4008 g_free(next_quote_str);
4011 if (quote_len > 0) {
4012 gtk_text_iter_forward_chars(&end, quote_len);
4013 if (gtk_text_iter_ends_line(&end)) {
4018 /* don't join itemized lines */
4019 if (compose_itemized_length(buffer, &end) > 0) {
4023 /* don't join signature separator */
4024 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4027 /* delete quote str */
4029 gtk_text_buffer_delete(buffer, &iter_, &end);
4031 /* don't join line breaks put by the user */
4033 gtk_text_iter_backward_char(&cur);
4034 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4035 gtk_text_iter_forward_char(&cur);
4039 gtk_text_iter_forward_char(&cur);
4040 /* delete linebreak and extra spaces */
4041 while (gtk_text_iter_backward_char(&cur)) {
4042 wc1 = gtk_text_iter_get_char(&cur);
4043 if (!g_unichar_isspace(wc1))
4048 while (!gtk_text_iter_ends_line(&cur)) {
4049 wc1 = gtk_text_iter_get_char(&cur);
4050 if (!g_unichar_isspace(wc1))
4052 gtk_text_iter_forward_char(&cur);
4055 if (!gtk_text_iter_equal(&prev, &next)) {
4058 mark = gtk_text_buffer_get_insert(buffer);
4059 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4060 if (gtk_text_iter_equal(&prev, &cur))
4062 gtk_text_buffer_delete(buffer, &prev, &next);
4066 /* insert space if required */
4067 gtk_text_iter_backward_char(&prev);
4068 wc1 = gtk_text_iter_get_char(&prev);
4069 wc2 = gtk_text_iter_get_char(&next);
4070 gtk_text_iter_forward_char(&next);
4071 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4072 pango_default_break(str, -1, NULL, attrs, 3);
4073 if (!attrs[1].is_line_break ||
4074 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4075 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4077 gtk_text_iter_backward_char(&iter_);
4078 gtk_text_buffer_place_cursor(buffer, &iter_);
4087 #define ADD_TXT_POS(bp_, ep_, pti_) \
4088 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4089 last = last->next; \
4090 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4091 last->next = NULL; \
4093 g_warning("alloc error scanning URIs\n"); \
4096 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4098 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4099 GtkTextBuffer *buffer;
4100 GtkTextIter iter, break_pos, end_of_line;
4101 gchar *quote_str = NULL;
4103 gboolean wrap_quote = prefs_common.linewrap_quote;
4104 gboolean prev_autowrap = compose->autowrap;
4105 gint startq_offset = -1, noq_offset = -1;
4106 gint uri_start = -1, uri_stop = -1;
4107 gint nouri_start = -1, nouri_stop = -1;
4108 gint num_blocks = 0;
4109 gint quotelevel = -1;
4110 gboolean modified = force;
4111 gboolean removed = FALSE;
4112 gboolean modified_before_remove = FALSE;
4114 gboolean start = TRUE;
4115 gint itemized_len = 0, rem_item_len = 0;
4116 gchar *itemized_chars = NULL;
4117 gboolean item_continuation = FALSE;
4122 if (compose->draft_timeout_tag == -2) {
4126 compose->autowrap = FALSE;
4128 buffer = gtk_text_view_get_buffer(text);
4129 undo_wrapping(compose->undostruct, TRUE);
4134 mark = gtk_text_buffer_get_insert(buffer);
4135 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4139 if (compose->draft_timeout_tag == -2) {
4140 if (gtk_text_iter_ends_line(&iter)) {
4141 while (gtk_text_iter_ends_line(&iter) &&
4142 gtk_text_iter_forward_line(&iter))
4145 while (gtk_text_iter_backward_line(&iter)) {
4146 if (gtk_text_iter_ends_line(&iter)) {
4147 gtk_text_iter_forward_line(&iter);
4153 /* move to line start */
4154 gtk_text_iter_set_line_offset(&iter, 0);
4157 itemized_len = compose_itemized_length(buffer, &iter);
4159 if (!itemized_len) {
4160 itemized_len = compose_left_offset_length(buffer, &iter);
4161 item_continuation = TRUE;
4165 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4167 /* go until paragraph end (empty line) */
4168 while (start || !gtk_text_iter_ends_line(&iter)) {
4169 gchar *scanpos = NULL;
4170 /* parse table - in order of priority */
4172 const gchar *needle; /* token */
4174 /* token search function */
4175 gchar *(*search) (const gchar *haystack,
4176 const gchar *needle);
4177 /* part parsing function */
4178 gboolean (*parse) (const gchar *start,
4179 const gchar *scanpos,
4183 /* part to URI function */
4184 gchar *(*build_uri) (const gchar *bp,
4188 static struct table parser[] = {
4189 {"http://", strcasestr, get_uri_part, make_uri_string},
4190 {"https://", strcasestr, get_uri_part, make_uri_string},
4191 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4192 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4193 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4194 {"www.", strcasestr, get_uri_part, make_http_string},
4195 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4196 {"@", strcasestr, get_email_part, make_email_string}
4198 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4199 gint last_index = PARSE_ELEMS;
4201 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4205 if (!prev_autowrap && num_blocks == 0) {
4207 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4208 G_CALLBACK(text_inserted),
4211 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4214 uri_start = uri_stop = -1;
4216 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4219 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4220 if (startq_offset == -1)
4221 startq_offset = gtk_text_iter_get_offset(&iter);
4222 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4223 if (quotelevel > 2) {
4224 /* recycle colors */
4225 if (prefs_common.recycle_quote_colors)
4234 if (startq_offset == -1)
4235 noq_offset = gtk_text_iter_get_offset(&iter);
4239 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4242 if (gtk_text_iter_ends_line(&iter)) {
4244 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4245 prefs_common.linewrap_len,
4247 GtkTextIter prev, next, cur;
4248 if (prev_autowrap != FALSE || force) {
4249 compose->automatic_break = TRUE;
4251 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4252 compose->automatic_break = FALSE;
4253 if (itemized_len && compose->autoindent) {
4254 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4255 if (!item_continuation)
4256 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4258 } else if (quote_str && wrap_quote) {
4259 compose->automatic_break = TRUE;
4261 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4262 compose->automatic_break = FALSE;
4263 if (itemized_len && compose->autoindent) {
4264 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4265 if (!item_continuation)
4266 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4270 /* remove trailing spaces */
4272 rem_item_len = itemized_len;
4273 while (compose->autoindent && rem_item_len-- > 0)
4274 gtk_text_iter_backward_char(&cur);
4275 gtk_text_iter_backward_char(&cur);
4278 while (!gtk_text_iter_starts_line(&cur)) {
4281 gtk_text_iter_backward_char(&cur);
4282 wc = gtk_text_iter_get_char(&cur);
4283 if (!g_unichar_isspace(wc))
4287 if (!gtk_text_iter_equal(&prev, &next)) {
4288 gtk_text_buffer_delete(buffer, &prev, &next);
4290 gtk_text_iter_forward_char(&break_pos);
4294 gtk_text_buffer_insert(buffer, &break_pos,
4298 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4300 /* move iter to current line start */
4301 gtk_text_iter_set_line_offset(&iter, 0);
4308 /* move iter to next line start */
4314 if (!prev_autowrap && num_blocks > 0) {
4316 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4317 G_CALLBACK(text_inserted),
4321 while (!gtk_text_iter_ends_line(&end_of_line)) {
4322 gtk_text_iter_forward_char(&end_of_line);
4324 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4326 nouri_start = gtk_text_iter_get_offset(&iter);
4327 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4329 walk_pos = gtk_text_iter_get_offset(&iter);
4330 /* FIXME: this looks phony. scanning for anything in the parse table */
4331 for (n = 0; n < PARSE_ELEMS; n++) {
4334 tmp = parser[n].search(walk, parser[n].needle);
4336 if (scanpos == NULL || tmp < scanpos) {
4345 /* check if URI can be parsed */
4346 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4347 (const gchar **)&ep, FALSE)
4348 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4352 strlen(parser[last_index].needle);
4355 uri_start = walk_pos + (bp - o_walk);
4356 uri_stop = walk_pos + (ep - o_walk);
4360 gtk_text_iter_forward_line(&iter);
4363 if (startq_offset != -1) {
4364 GtkTextIter startquote, endquote;
4365 gtk_text_buffer_get_iter_at_offset(
4366 buffer, &startquote, startq_offset);
4369 switch (quotelevel) {
4371 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4372 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4373 gtk_text_buffer_apply_tag_by_name(
4374 buffer, "quote0", &startquote, &endquote);
4375 gtk_text_buffer_remove_tag_by_name(
4376 buffer, "quote1", &startquote, &endquote);
4377 gtk_text_buffer_remove_tag_by_name(
4378 buffer, "quote2", &startquote, &endquote);
4383 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4384 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4385 gtk_text_buffer_apply_tag_by_name(
4386 buffer, "quote1", &startquote, &endquote);
4387 gtk_text_buffer_remove_tag_by_name(
4388 buffer, "quote0", &startquote, &endquote);
4389 gtk_text_buffer_remove_tag_by_name(
4390 buffer, "quote2", &startquote, &endquote);
4395 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4396 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4397 gtk_text_buffer_apply_tag_by_name(
4398 buffer, "quote2", &startquote, &endquote);
4399 gtk_text_buffer_remove_tag_by_name(
4400 buffer, "quote0", &startquote, &endquote);
4401 gtk_text_buffer_remove_tag_by_name(
4402 buffer, "quote1", &startquote, &endquote);
4408 } else if (noq_offset != -1) {
4409 GtkTextIter startnoquote, endnoquote;
4410 gtk_text_buffer_get_iter_at_offset(
4411 buffer, &startnoquote, noq_offset);
4414 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4415 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4416 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4417 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4418 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4419 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4420 gtk_text_buffer_remove_tag_by_name(
4421 buffer, "quote0", &startnoquote, &endnoquote);
4422 gtk_text_buffer_remove_tag_by_name(
4423 buffer, "quote1", &startnoquote, &endnoquote);
4424 gtk_text_buffer_remove_tag_by_name(
4425 buffer, "quote2", &startnoquote, &endnoquote);
4431 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4432 GtkTextIter nouri_start_iter, nouri_end_iter;
4433 gtk_text_buffer_get_iter_at_offset(
4434 buffer, &nouri_start_iter, nouri_start);
4435 gtk_text_buffer_get_iter_at_offset(
4436 buffer, &nouri_end_iter, nouri_stop);
4437 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4438 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4439 gtk_text_buffer_remove_tag_by_name(
4440 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4441 modified_before_remove = modified;
4446 if (uri_start >= 0 && uri_stop > 0) {
4447 GtkTextIter uri_start_iter, uri_end_iter, back;
4448 gtk_text_buffer_get_iter_at_offset(
4449 buffer, &uri_start_iter, uri_start);
4450 gtk_text_buffer_get_iter_at_offset(
4451 buffer, &uri_end_iter, uri_stop);
4452 back = uri_end_iter;
4453 gtk_text_iter_backward_char(&back);
4454 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4455 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4456 gtk_text_buffer_apply_tag_by_name(
4457 buffer, "link", &uri_start_iter, &uri_end_iter);
4459 if (removed && !modified_before_remove) {
4465 // debug_print("not modified, out after %d lines\n", lines);
4469 // debug_print("modified, out after %d lines\n", lines);
4471 g_free(itemized_chars);
4474 undo_wrapping(compose->undostruct, FALSE);
4475 compose->autowrap = prev_autowrap;
4480 void compose_action_cb(void *data)
4482 Compose *compose = (Compose *)data;
4483 compose_wrap_all(compose);
4486 static void compose_wrap_all(Compose *compose)
4488 compose_wrap_all_full(compose, FALSE);
4491 static void compose_wrap_all_full(Compose *compose, gboolean force)
4493 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4494 GtkTextBuffer *buffer;
4496 gboolean modified = TRUE;
4498 buffer = gtk_text_view_get_buffer(text);
4500 gtk_text_buffer_get_start_iter(buffer, &iter);
4501 while (!gtk_text_iter_is_end(&iter) && modified)
4502 modified = compose_beautify_paragraph(compose, &iter, force);
4506 static void compose_set_title(Compose *compose)
4512 edited = compose->modified ? _(" [Edited]") : "";
4514 subject = gtk_editable_get_chars(
4515 GTK_EDITABLE(compose->subject_entry), 0, -1);
4517 #ifndef GENERIC_UMPC
4518 if (subject && strlen(subject))
4519 str = g_strdup_printf(_("%s - Compose message%s"),
4522 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4524 str = g_strdup(_("Compose message"));
4527 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4533 * compose_current_mail_account:
4535 * Find a current mail account (the currently selected account, or the
4536 * default account, if a news account is currently selected). If a
4537 * mail account cannot be found, display an error message.
4539 * Return value: Mail account, or NULL if not found.
4541 static PrefsAccount *
4542 compose_current_mail_account(void)
4546 if (cur_account && cur_account->protocol != A_NNTP)
4549 ac = account_get_default();
4550 if (!ac || ac->protocol == A_NNTP) {
4551 alertpanel_error(_("Account for sending mail is not specified.\n"
4552 "Please select a mail account before sending."));
4559 #define QUOTE_IF_REQUIRED(out, str) \
4561 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4565 len = strlen(str) + 3; \
4566 if ((__tmp = alloca(len)) == NULL) { \
4567 g_warning("can't allocate memory\n"); \
4568 g_string_free(header, TRUE); \
4571 g_snprintf(__tmp, len, "\"%s\"", str); \
4576 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4577 g_warning("can't allocate memory\n"); \
4578 g_string_free(header, TRUE); \
4581 strcpy(__tmp, str); \
4587 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4589 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4593 len = strlen(str) + 3; \
4594 if ((__tmp = alloca(len)) == NULL) { \
4595 g_warning("can't allocate memory\n"); \
4598 g_snprintf(__tmp, len, "\"%s\"", str); \
4603 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4604 g_warning("can't allocate memory\n"); \
4607 strcpy(__tmp, str); \
4613 static void compose_select_account(Compose *compose, PrefsAccount *account,
4616 gchar *from = NULL, *email, *header;
4617 ComposeHeaderEntry *header_entry;
4619 cm_return_if_fail(account != NULL);
4621 email = g_utf8_strdown(compose->account->address, -1);
4622 g_hash_table_remove(compose->email_hashtable, email);
4625 compose->account = account;
4626 g_hash_table_insert(compose->email_hashtable,
4627 g_utf8_strdown(account->address, -1),
4628 GUINT_TO_POINTER(1));
4630 if (account->name && *account->name) {
4632 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4633 from = g_strdup_printf("%s <%s>",
4634 buf, account->address);
4635 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4637 from = g_strdup_printf("<%s>",
4639 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4644 compose_set_title(compose);
4646 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4647 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4649 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4650 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4651 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4653 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4655 activate_privacy_system(compose, account, FALSE);
4657 if (!init && compose->mode != COMPOSE_REDIRECT) {
4658 undo_block(compose->undostruct);
4659 compose_insert_sig(compose, TRUE);
4660 undo_unblock(compose->undostruct);
4663 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4664 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4666 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4667 if (account->protocol == A_NNTP) {
4668 if (!strcmp(header, _("To:")))
4669 combobox_select_by_text(
4670 GTK_COMBO_BOX(header_entry->combo),
4673 if (!strcmp(header, _("Newsgroups:")))
4674 combobox_select_by_text(
4675 GTK_COMBO_BOX(header_entry->combo),
4683 /* use account's dict info if set */
4684 if (compose->gtkaspell) {
4685 if (account->enable_default_dictionary)
4686 gtkaspell_change_dict(compose->gtkaspell,
4687 account->default_dictionary, FALSE);
4688 if (account->enable_default_alt_dictionary)
4689 gtkaspell_change_alt_dict(compose->gtkaspell,
4690 account->default_alt_dictionary);
4691 if (account->enable_default_dictionary
4692 || account->enable_default_alt_dictionary)
4693 compose_spell_menu_changed(compose);
4698 gboolean compose_check_for_valid_recipient(Compose *compose) {
4699 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4700 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4701 gboolean recipient_found = FALSE;
4705 /* free to and newsgroup list */
4706 slist_free_strings(compose->to_list);
4707 g_slist_free(compose->to_list);
4708 compose->to_list = NULL;
4710 slist_free_strings(compose->newsgroup_list);
4711 g_slist_free(compose->newsgroup_list);
4712 compose->newsgroup_list = NULL;
4714 /* search header entries for to and newsgroup entries */
4715 for (list = compose->header_list; list; list = list->next) {
4718 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4719 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4722 if (entry[0] != '\0') {
4723 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4724 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4725 compose->to_list = address_list_append(compose->to_list, entry);
4726 recipient_found = TRUE;
4729 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4730 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4731 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4732 recipient_found = TRUE;
4739 return recipient_found;
4742 static gboolean compose_check_for_set_recipients(Compose *compose)
4744 if (compose->account->set_autocc && compose->account->auto_cc) {
4745 gboolean found_other = FALSE;
4747 /* search header entries for to and newsgroup entries */
4748 for (list = compose->header_list; list; list = list->next) {
4751 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4752 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4755 if (strcmp(entry, compose->account->auto_cc)
4756 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4766 if (compose->batch) {
4767 gtk_widget_show_all(compose->window);
4769 aval = alertpanel(_("Send"),
4770 _("The only recipient is the default CC address. Send anyway?"),
4771 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4772 if (aval != G_ALERTALTERNATE)
4776 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4777 gboolean found_other = FALSE;
4779 /* search header entries for to and newsgroup entries */
4780 for (list = compose->header_list; list; list = list->next) {
4783 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4784 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4787 if (strcmp(entry, compose->account->auto_bcc)
4788 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4798 if (compose->batch) {
4799 gtk_widget_show_all(compose->window);
4801 aval = alertpanel(_("Send"),
4802 _("The only recipient is the default BCC address. Send anyway?"),
4803 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4804 if (aval != G_ALERTALTERNATE)
4811 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4815 if (compose_check_for_valid_recipient(compose) == FALSE) {
4816 if (compose->batch) {
4817 gtk_widget_show_all(compose->window);
4819 alertpanel_error(_("Recipient is not specified."));
4823 if (compose_check_for_set_recipients(compose) == FALSE) {
4827 if (!compose->batch) {
4828 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4829 if (*str == '\0' && check_everything == TRUE &&
4830 compose->mode != COMPOSE_REDIRECT) {
4832 gchar *button_label;
4835 if (compose->sending)
4836 button_label = _("+_Send");
4838 button_label = _("+_Queue");
4839 message = g_strdup_printf(_("Subject is empty. %s"),
4840 compose->sending?_("Send it anyway?"):
4841 _("Queue it anyway?"));
4843 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4844 GTK_STOCK_CANCEL, button_label, NULL);
4846 if (aval != G_ALERTALTERNATE)
4851 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4857 gint compose_send(Compose *compose)
4860 FolderItem *folder = NULL;
4862 gchar *msgpath = NULL;
4863 gboolean discard_window = FALSE;
4864 gchar *errstr = NULL;
4865 gchar *tmsgid = NULL;
4866 MainWindow *mainwin = mainwindow_get_mainwindow();
4867 gboolean queued_removed = FALSE;
4869 if (prefs_common.send_dialog_invisible
4870 || compose->batch == TRUE)
4871 discard_window = TRUE;
4873 compose_allow_user_actions (compose, FALSE);
4874 compose->sending = TRUE;
4876 if (compose_check_entries(compose, TRUE) == FALSE) {
4877 if (compose->batch) {
4878 gtk_widget_show_all(compose->window);
4884 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4887 if (compose->batch) {
4888 gtk_widget_show_all(compose->window);
4891 alertpanel_error(_("Could not queue message for sending:\n\n"
4892 "Charset conversion failed."));
4893 } else if (val == -5) {
4894 alertpanel_error(_("Could not queue message for sending:\n\n"
4895 "Couldn't get recipient encryption key."));
4896 } else if (val == -6) {
4898 } else if (val == -3) {
4899 if (privacy_peek_error())
4900 alertpanel_error(_("Could not queue message for sending:\n\n"
4901 "Signature failed: %s"), privacy_get_error());
4902 } else if (val == -2 && errno != 0) {
4903 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4905 alertpanel_error(_("Could not queue message for sending."));
4910 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4911 if (discard_window) {
4912 compose->sending = FALSE;
4913 compose_close(compose);
4914 /* No more compose access in the normal codepath
4915 * after this point! */
4920 alertpanel_error(_("The message was queued but could not be "
4921 "sent.\nUse \"Send queued messages\" from "
4922 "the main window to retry."));
4923 if (!discard_window) {
4930 if (msgpath == NULL) {
4931 msgpath = folder_item_fetch_msg(folder, msgnum);
4932 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4935 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4936 claws_unlink(msgpath);
4939 if (!discard_window) {
4941 if (!queued_removed)
4942 folder_item_remove_msg(folder, msgnum);
4943 folder_item_scan(folder);
4945 /* make sure we delete that */
4946 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4948 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4949 folder_item_remove_msg(folder, tmp->msgnum);
4950 procmsg_msginfo_free(tmp);
4957 if (!queued_removed)
4958 folder_item_remove_msg(folder, msgnum);
4959 folder_item_scan(folder);
4961 /* make sure we delete that */
4962 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4964 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4965 folder_item_remove_msg(folder, tmp->msgnum);
4966 procmsg_msginfo_free(tmp);
4969 if (!discard_window) {
4970 compose->sending = FALSE;
4971 compose_allow_user_actions (compose, TRUE);
4972 compose_close(compose);
4976 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4977 "the main window to retry."), errstr);
4980 alertpanel_error_log(_("The message was queued but could not be "
4981 "sent.\nUse \"Send queued messages\" from "
4982 "the main window to retry."));
4984 if (!discard_window) {
4993 toolbar_main_set_sensitive(mainwin);
4994 main_window_set_menu_sensitive(mainwin);
5000 compose_allow_user_actions (compose, TRUE);
5001 compose->sending = FALSE;
5002 compose->modified = TRUE;
5003 toolbar_main_set_sensitive(mainwin);
5004 main_window_set_menu_sensitive(mainwin);
5009 static gboolean compose_use_attach(Compose *compose)
5011 GtkTreeModel *model = gtk_tree_view_get_model
5012 (GTK_TREE_VIEW(compose->attach_clist));
5013 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5016 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5019 gchar buf[BUFFSIZE];
5021 gboolean first_to_address;
5022 gboolean first_cc_address;
5024 ComposeHeaderEntry *headerentry;
5025 const gchar *headerentryname;
5026 const gchar *cc_hdr;
5027 const gchar *to_hdr;
5028 gboolean err = FALSE;
5030 debug_print("Writing redirect header\n");
5032 cc_hdr = prefs_common_translated_header_name("Cc:");
5033 to_hdr = prefs_common_translated_header_name("To:");
5035 first_to_address = TRUE;
5036 for (list = compose->header_list; list; list = list->next) {
5037 headerentry = ((ComposeHeaderEntry *)list->data);
5038 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5040 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5041 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5042 Xstrdup_a(str, entstr, return -1);
5044 if (str[0] != '\0') {
5045 compose_convert_header
5046 (compose, buf, sizeof(buf), str,
5047 strlen("Resent-To") + 2, TRUE);
5049 if (first_to_address) {
5050 err |= (fprintf(fp, "Resent-To: ") < 0);
5051 first_to_address = FALSE;
5053 err |= (fprintf(fp, ",") < 0);
5055 err |= (fprintf(fp, "%s", buf) < 0);
5059 if (!first_to_address) {
5060 err |= (fprintf(fp, "\n") < 0);
5063 first_cc_address = TRUE;
5064 for (list = compose->header_list; list; list = list->next) {
5065 headerentry = ((ComposeHeaderEntry *)list->data);
5066 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5068 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5069 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5070 Xstrdup_a(str, strg, return -1);
5072 if (str[0] != '\0') {
5073 compose_convert_header
5074 (compose, buf, sizeof(buf), str,
5075 strlen("Resent-Cc") + 2, TRUE);
5077 if (first_cc_address) {
5078 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5079 first_cc_address = FALSE;
5081 err |= (fprintf(fp, ",") < 0);
5083 err |= (fprintf(fp, "%s", buf) < 0);
5087 if (!first_cc_address) {
5088 err |= (fprintf(fp, "\n") < 0);
5091 return (err ? -1:0);
5094 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5096 gchar buf[BUFFSIZE];
5098 const gchar *entstr;
5099 /* struct utsname utsbuf; */
5100 gboolean err = FALSE;
5102 cm_return_val_if_fail(fp != NULL, -1);
5103 cm_return_val_if_fail(compose->account != NULL, -1);
5104 cm_return_val_if_fail(compose->account->address != NULL, -1);
5107 get_rfc822_date(buf, sizeof(buf));
5108 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5111 if (compose->account->name && *compose->account->name) {
5112 compose_convert_header
5113 (compose, buf, sizeof(buf), compose->account->name,
5114 strlen("From: "), TRUE);
5115 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5116 buf, compose->account->address) < 0);
5118 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5121 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5122 if (*entstr != '\0') {
5123 Xstrdup_a(str, entstr, return -1);
5126 compose_convert_header(compose, buf, sizeof(buf), str,
5127 strlen("Subject: "), FALSE);
5128 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5132 /* Resent-Message-ID */
5133 if (compose->account->set_domain && compose->account->domain) {
5134 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5135 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5136 g_snprintf(buf, sizeof(buf), "%s",
5137 strchr(compose->account->address, '@') ?
5138 strchr(compose->account->address, '@')+1 :
5139 compose->account->address);
5141 g_snprintf(buf, sizeof(buf), "%s", "");
5144 if (compose->account->gen_msgid) {
5146 if (compose->account->msgid_with_addr) {
5147 addr = compose->account->address;
5149 generate_msgid(buf, sizeof(buf), addr);
5150 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5151 compose->msgid = g_strdup(buf);
5153 compose->msgid = NULL;
5156 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5159 /* separator between header and body */
5160 err |= (fputs("\n", fp) == EOF);
5162 return (err ? -1:0);
5165 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5169 gchar buf[BUFFSIZE];
5171 gboolean skip = FALSE;
5172 gboolean err = FALSE;
5173 gchar *not_included[]={
5174 "Return-Path:", "Delivered-To:", "Received:",
5175 "Subject:", "X-UIDL:", "AF:",
5176 "NF:", "PS:", "SRH:",
5177 "SFN:", "DSR:", "MID:",
5178 "CFG:", "PT:", "S:",
5179 "RQ:", "SSV:", "NSV:",
5180 "SSH:", "R:", "MAID:",
5181 "NAID:", "RMID:", "FMID:",
5182 "SCF:", "RRCPT:", "NG:",
5183 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5184 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5185 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5186 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5187 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5190 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5191 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5195 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5197 for (i = 0; not_included[i] != NULL; i++) {
5198 if (g_ascii_strncasecmp(buf, not_included[i],
5199 strlen(not_included[i])) == 0) {
5206 if (fputs(buf, fdest) == -1)
5209 if (!prefs_common.redirect_keep_from) {
5210 if (g_ascii_strncasecmp(buf, "From:",
5211 strlen("From:")) == 0) {
5212 err |= (fputs(" (by way of ", fdest) == EOF);
5213 if (compose->account->name
5214 && *compose->account->name) {
5215 compose_convert_header
5216 (compose, buf, sizeof(buf),
5217 compose->account->name,
5220 err |= (fprintf(fdest, "%s <%s>",
5222 compose->account->address) < 0);
5224 err |= (fprintf(fdest, "%s",
5225 compose->account->address) < 0);
5226 err |= (fputs(")", fdest) == EOF);
5230 if (fputs("\n", fdest) == -1)
5237 if (compose_redirect_write_headers(compose, fdest))
5240 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5241 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5254 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5256 GtkTextBuffer *buffer;
5257 GtkTextIter start, end;
5260 const gchar *out_codeset;
5261 EncodingType encoding = ENC_UNKNOWN;
5262 MimeInfo *mimemsg, *mimetext;
5264 const gchar *src_codeset = CS_INTERNAL;
5265 gchar *from_addr = NULL;
5266 gchar *from_name = NULL;
5268 if (action == COMPOSE_WRITE_FOR_SEND)
5269 attach_parts = TRUE;
5271 /* create message MimeInfo */
5272 mimemsg = procmime_mimeinfo_new();
5273 mimemsg->type = MIMETYPE_MESSAGE;
5274 mimemsg->subtype = g_strdup("rfc822");
5275 mimemsg->content = MIMECONTENT_MEM;
5276 mimemsg->tmp = TRUE; /* must free content later */
5277 mimemsg->data.mem = compose_get_header(compose);
5279 /* Create text part MimeInfo */
5280 /* get all composed text */
5281 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5282 gtk_text_buffer_get_start_iter(buffer, &start);
5283 gtk_text_buffer_get_end_iter(buffer, &end);
5284 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5286 out_codeset = conv_get_charset_str(compose->out_encoding);
5288 if (!out_codeset && is_ascii_str(chars)) {
5289 out_codeset = CS_US_ASCII;
5290 } else if (prefs_common.outgoing_fallback_to_ascii &&
5291 is_ascii_str(chars)) {
5292 out_codeset = CS_US_ASCII;
5293 encoding = ENC_7BIT;
5297 gchar *test_conv_global_out = NULL;
5298 gchar *test_conv_reply = NULL;
5300 /* automatic mode. be automatic. */
5301 codeconv_set_strict(TRUE);
5303 out_codeset = conv_get_outgoing_charset_str();
5305 debug_print("trying to convert to %s\n", out_codeset);
5306 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5309 if (!test_conv_global_out && compose->orig_charset
5310 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5311 out_codeset = compose->orig_charset;
5312 debug_print("failure; trying to convert to %s\n", out_codeset);
5313 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5316 if (!test_conv_global_out && !test_conv_reply) {
5318 out_codeset = CS_INTERNAL;
5319 debug_print("failure; finally using %s\n", out_codeset);
5321 g_free(test_conv_global_out);
5322 g_free(test_conv_reply);
5323 codeconv_set_strict(FALSE);
5326 if (encoding == ENC_UNKNOWN) {
5327 if (prefs_common.encoding_method == CTE_BASE64)
5328 encoding = ENC_BASE64;
5329 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5330 encoding = ENC_QUOTED_PRINTABLE;
5331 else if (prefs_common.encoding_method == CTE_8BIT)
5332 encoding = ENC_8BIT;
5334 encoding = procmime_get_encoding_for_charset(out_codeset);
5337 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5338 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5340 if (action == COMPOSE_WRITE_FOR_SEND) {
5341 codeconv_set_strict(TRUE);
5342 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5343 codeconv_set_strict(FALSE);
5349 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5350 "to the specified %s charset.\n"
5351 "Send it as %s?"), out_codeset, src_codeset);
5352 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5353 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5356 if (aval != G_ALERTALTERNATE) {
5361 out_codeset = src_codeset;
5367 out_codeset = src_codeset;
5372 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5373 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5374 strstr(buf, "\nFrom ") != NULL) {
5375 encoding = ENC_QUOTED_PRINTABLE;
5379 mimetext = procmime_mimeinfo_new();
5380 mimetext->content = MIMECONTENT_MEM;
5381 mimetext->tmp = TRUE; /* must free content later */
5382 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5383 * and free the data, which we need later. */
5384 mimetext->data.mem = g_strdup(buf);
5385 mimetext->type = MIMETYPE_TEXT;
5386 mimetext->subtype = g_strdup("plain");
5387 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5388 g_strdup(out_codeset));
5390 /* protect trailing spaces when signing message */
5391 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5392 privacy_system_can_sign(compose->privacy_system)) {
5393 encoding = ENC_QUOTED_PRINTABLE;
5396 debug_print("main text: %zd bytes encoded as %s in %d\n",
5397 strlen(buf), out_codeset, encoding);
5399 /* check for line length limit */
5400 if (action == COMPOSE_WRITE_FOR_SEND &&
5401 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5402 check_line_length(buf, 1000, &line) < 0) {
5406 msg = g_strdup_printf
5407 (_("Line %d exceeds the line length limit (998 bytes).\n"
5408 "The contents of the message might be broken on the way to the delivery.\n"
5410 "Send it anyway?"), line + 1);
5411 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5413 if (aval != G_ALERTALTERNATE) {
5419 if (encoding != ENC_UNKNOWN)
5420 procmime_encode_content(mimetext, encoding);
5422 /* append attachment parts */
5423 if (compose_use_attach(compose) && attach_parts) {
5424 MimeInfo *mimempart;
5425 gchar *boundary = NULL;
5426 mimempart = procmime_mimeinfo_new();
5427 mimempart->content = MIMECONTENT_EMPTY;
5428 mimempart->type = MIMETYPE_MULTIPART;
5429 mimempart->subtype = g_strdup("mixed");
5433 boundary = generate_mime_boundary(NULL);
5434 } while (strstr(buf, boundary) != NULL);
5436 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5439 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5441 g_node_append(mimempart->node, mimetext->node);
5442 g_node_append(mimemsg->node, mimempart->node);
5444 if (compose_add_attachments(compose, mimempart) < 0)
5447 g_node_append(mimemsg->node, mimetext->node);
5451 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5452 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5453 /* extract name and address */
5454 if (strstr(spec, " <") && strstr(spec, ">")) {
5455 from_addr = g_strdup(strrchr(spec, '<')+1);
5456 *(strrchr(from_addr, '>')) = '\0';
5457 from_name = g_strdup(spec);
5458 *(strrchr(from_name, '<')) = '\0';
5465 /* sign message if sending */
5466 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5467 privacy_system_can_sign(compose->privacy_system))
5468 if (!privacy_sign(compose->privacy_system, mimemsg,
5469 compose->account, from_addr)) {
5476 procmime_write_mimeinfo(mimemsg, fp);
5478 procmime_mimeinfo_free_all(mimemsg);
5483 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5485 GtkTextBuffer *buffer;
5486 GtkTextIter start, end;
5491 if ((fp = g_fopen(file, "wb")) == NULL) {
5492 FILE_OP_ERROR(file, "fopen");
5496 /* chmod for security */
5497 if (change_file_mode_rw(fp, file) < 0) {
5498 FILE_OP_ERROR(file, "chmod");
5499 g_warning("can't change file mode\n");
5502 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5503 gtk_text_buffer_get_start_iter(buffer, &start);
5504 gtk_text_buffer_get_end_iter(buffer, &end);
5505 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5507 chars = conv_codeset_strdup
5508 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5511 if (!chars) return -1;
5514 len = strlen(chars);
5515 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5516 FILE_OP_ERROR(file, "fwrite");
5525 if (fclose(fp) == EOF) {
5526 FILE_OP_ERROR(file, "fclose");
5533 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5536 MsgInfo *msginfo = compose->targetinfo;
5538 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5539 if (!msginfo) return -1;
5541 if (!force && MSG_IS_LOCKED(msginfo->flags))
5544 item = msginfo->folder;
5545 cm_return_val_if_fail(item != NULL, -1);
5547 if (procmsg_msg_exist(msginfo) &&
5548 (folder_has_parent_of_type(item, F_QUEUE) ||
5549 folder_has_parent_of_type(item, F_DRAFT)
5550 || msginfo == compose->autosaved_draft)) {
5551 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5552 g_warning("can't remove the old message\n");
5555 debug_print("removed reedit target %d\n", msginfo->msgnum);
5562 static void compose_remove_draft(Compose *compose)
5565 MsgInfo *msginfo = compose->targetinfo;
5566 drafts = account_get_special_folder(compose->account, F_DRAFT);
5568 if (procmsg_msg_exist(msginfo)) {
5569 folder_item_remove_msg(drafts, msginfo->msgnum);
5574 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5575 gboolean remove_reedit_target)
5577 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5580 static gboolean compose_warn_encryption(Compose *compose)
5582 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5583 AlertValue val = G_ALERTALTERNATE;
5585 if (warning == NULL)
5588 val = alertpanel_full(_("Encryption warning"), warning,
5589 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5590 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5591 if (val & G_ALERTDISABLE) {
5592 val &= ~G_ALERTDISABLE;
5593 if (val == G_ALERTALTERNATE)
5594 privacy_inhibit_encrypt_warning(compose->privacy_system,
5598 if (val == G_ALERTALTERNATE) {
5605 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5606 gchar **msgpath, gboolean check_subject,
5607 gboolean remove_reedit_target)
5614 static gboolean lock = FALSE;
5615 PrefsAccount *mailac = NULL, *newsac = NULL;
5616 gboolean err = FALSE;
5618 debug_print("queueing message...\n");
5619 cm_return_val_if_fail(compose->account != NULL, -1);
5623 if (compose_check_entries(compose, check_subject) == FALSE) {
5625 if (compose->batch) {
5626 gtk_widget_show_all(compose->window);
5631 if (!compose->to_list && !compose->newsgroup_list) {
5632 g_warning("can't get recipient list.");
5637 if (compose->to_list) {
5638 if (compose->account->protocol != A_NNTP)
5639 mailac = compose->account;
5640 else if (cur_account && cur_account->protocol != A_NNTP)
5641 mailac = cur_account;
5642 else if (!(mailac = compose_current_mail_account())) {
5644 alertpanel_error(_("No account for sending mails available!"));
5649 if (compose->newsgroup_list) {
5650 if (compose->account->protocol == A_NNTP)
5651 newsac = compose->account;
5654 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5659 /* write queue header */
5660 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5661 G_DIR_SEPARATOR, compose, (guint) rand());
5662 debug_print("queuing to %s\n", tmp);
5663 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5664 FILE_OP_ERROR(tmp, "fopen");
5670 if (change_file_mode_rw(fp, tmp) < 0) {
5671 FILE_OP_ERROR(tmp, "chmod");
5672 g_warning("can't change file mode\n");
5675 /* queueing variables */
5676 err |= (fprintf(fp, "AF:\n") < 0);
5677 err |= (fprintf(fp, "NF:0\n") < 0);
5678 err |= (fprintf(fp, "PS:10\n") < 0);
5679 err |= (fprintf(fp, "SRH:1\n") < 0);
5680 err |= (fprintf(fp, "SFN:\n") < 0);
5681 err |= (fprintf(fp, "DSR:\n") < 0);
5683 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5685 err |= (fprintf(fp, "MID:\n") < 0);
5686 err |= (fprintf(fp, "CFG:\n") < 0);
5687 err |= (fprintf(fp, "PT:0\n") < 0);
5688 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5689 err |= (fprintf(fp, "RQ:\n") < 0);
5691 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5693 err |= (fprintf(fp, "SSV:\n") < 0);
5695 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5697 err |= (fprintf(fp, "NSV:\n") < 0);
5698 err |= (fprintf(fp, "SSH:\n") < 0);
5699 /* write recepient list */
5700 if (compose->to_list) {
5701 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5702 for (cur = compose->to_list->next; cur != NULL;
5704 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5705 err |= (fprintf(fp, "\n") < 0);
5707 /* write newsgroup list */
5708 if (compose->newsgroup_list) {
5709 err |= (fprintf(fp, "NG:") < 0);
5710 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5711 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5712 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5713 err |= (fprintf(fp, "\n") < 0);
5715 /* Sylpheed account IDs */
5717 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5719 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5722 if (compose->privacy_system != NULL) {
5723 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5724 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5725 if (compose->use_encryption) {
5727 if (!compose_warn_encryption(compose)) {
5734 if (mailac && mailac->encrypt_to_self) {
5735 GSList *tmp_list = g_slist_copy(compose->to_list);
5736 tmp_list = g_slist_append(tmp_list, compose->account->address);
5737 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5738 g_slist_free(tmp_list);
5740 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5742 if (encdata != NULL) {
5743 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5744 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5745 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5747 } /* else we finally dont want to encrypt */
5749 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5750 /* and if encdata was null, it means there's been a problem in
5762 /* Save copy folder */
5763 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5764 gchar *savefolderid;
5766 savefolderid = compose_get_save_to(compose);
5767 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5768 g_free(savefolderid);
5770 /* Save copy folder */
5771 if (compose->return_receipt) {
5772 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5774 /* Message-ID of message replying to */
5775 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5778 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5779 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5782 /* Message-ID of message forwarding to */
5783 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5786 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5787 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5791 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5792 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5794 /* end of headers */
5795 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5797 if (compose->redirect_filename != NULL) {
5798 if (compose_redirect_write_to_file(compose, fp) < 0) {
5807 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5812 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5816 g_warning("failed to write queue message\n");
5823 if (fclose(fp) == EOF) {
5824 FILE_OP_ERROR(tmp, "fclose");
5831 if (item && *item) {
5834 queue = account_get_special_folder(compose->account, F_QUEUE);
5837 g_warning("can't find queue folder\n");
5843 folder_item_scan(queue);
5844 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5845 g_warning("can't queue the message\n");
5852 if (msgpath == NULL) {
5858 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5859 compose_remove_reedit_target(compose, FALSE);
5862 if ((msgnum != NULL) && (item != NULL)) {
5870 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5873 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5875 struct stat statbuf;
5876 gchar *type, *subtype;
5877 GtkTreeModel *model;
5880 model = gtk_tree_view_get_model(tree_view);
5882 if (!gtk_tree_model_get_iter_first(model, &iter))
5885 gtk_tree_model_get(model, &iter,
5889 if (!is_file_exist(ainfo->file)) {
5890 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5891 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5892 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5894 if (val == G_ALERTDEFAULT) {
5899 mimepart = procmime_mimeinfo_new();
5900 mimepart->content = MIMECONTENT_FILE;
5901 mimepart->data.filename = g_strdup(ainfo->file);
5902 mimepart->tmp = FALSE; /* or we destroy our attachment */
5903 mimepart->offset = 0;
5905 g_stat(ainfo->file, &statbuf);
5906 mimepart->length = statbuf.st_size;
5908 type = g_strdup(ainfo->content_type);
5910 if (!strchr(type, '/')) {
5912 type = g_strdup("application/octet-stream");
5915 subtype = strchr(type, '/') + 1;
5916 *(subtype - 1) = '\0';
5917 mimepart->type = procmime_get_media_type(type);
5918 mimepart->subtype = g_strdup(subtype);
5921 if (mimepart->type == MIMETYPE_MESSAGE &&
5922 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5923 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5926 if (mimepart->type == MIMETYPE_APPLICATION &&
5927 !strcmp2(mimepart->subtype, "octet-stream"))
5928 g_hash_table_insert(mimepart->typeparameters,
5929 g_strdup("name"), g_strdup(ainfo->name));
5930 g_hash_table_insert(mimepart->dispositionparameters,
5931 g_strdup("filename"), g_strdup(ainfo->name));
5932 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5936 if (compose->use_signing) {
5937 if (ainfo->encoding == ENC_7BIT)
5938 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5939 else if (ainfo->encoding == ENC_8BIT)
5940 ainfo->encoding = ENC_BASE64;
5943 procmime_encode_content(mimepart, ainfo->encoding);
5945 g_node_append(parent->node, mimepart->node);
5946 } while (gtk_tree_model_iter_next(model, &iter));
5951 #define IS_IN_CUSTOM_HEADER(header) \
5952 (compose->account->add_customhdr && \
5953 custom_header_find(compose->account->customhdr_list, header) != NULL)
5955 static void compose_add_headerfield_from_headerlist(Compose *compose,
5957 const gchar *fieldname,
5958 const gchar *seperator)
5960 gchar *str, *fieldname_w_colon;
5961 gboolean add_field = FALSE;
5963 ComposeHeaderEntry *headerentry;
5964 const gchar *headerentryname;
5965 const gchar *trans_fieldname;
5968 if (IS_IN_CUSTOM_HEADER(fieldname))
5971 debug_print("Adding %s-fields\n", fieldname);
5973 fieldstr = g_string_sized_new(64);
5975 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5976 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5978 for (list = compose->header_list; list; list = list->next) {
5979 headerentry = ((ComposeHeaderEntry *)list->data);
5980 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5982 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5983 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5985 if (str[0] != '\0') {
5987 g_string_append(fieldstr, seperator);
5988 g_string_append(fieldstr, str);
5997 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5998 compose_convert_header
5999 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6000 strlen(fieldname) + 2, TRUE);
6001 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6005 g_free(fieldname_w_colon);
6006 g_string_free(fieldstr, TRUE);
6011 static gchar *compose_get_header(Compose *compose)
6013 gchar buf[BUFFSIZE];
6014 const gchar *entry_str;
6018 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6020 gchar *from_name = NULL, *from_address = NULL;
6023 cm_return_val_if_fail(compose->account != NULL, NULL);
6024 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6026 header = g_string_sized_new(64);
6029 get_rfc822_date(buf, sizeof(buf));
6030 g_string_append_printf(header, "Date: %s\n", buf);
6034 if (compose->account->name && *compose->account->name) {
6036 QUOTE_IF_REQUIRED(buf, compose->account->name);
6037 tmp = g_strdup_printf("%s <%s>",
6038 buf, compose->account->address);
6040 tmp = g_strdup_printf("%s",
6041 compose->account->address);
6043 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6044 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6046 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6047 from_address = g_strdup(compose->account->address);
6049 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6050 /* extract name and address */
6051 if (strstr(spec, " <") && strstr(spec, ">")) {
6052 from_address = g_strdup(strrchr(spec, '<')+1);
6053 *(strrchr(from_address, '>')) = '\0';
6054 from_name = g_strdup(spec);
6055 *(strrchr(from_name, '<')) = '\0';
6058 from_address = g_strdup(spec);
6065 if (from_name && *from_name) {
6066 compose_convert_header
6067 (compose, buf, sizeof(buf), from_name,
6068 strlen("From: "), TRUE);
6069 QUOTE_IF_REQUIRED(name, buf);
6071 g_string_append_printf(header, "From: %s <%s>\n",
6072 name, from_address);
6074 g_string_append_printf(header, "From: %s\n", from_address);
6077 g_free(from_address);
6080 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6083 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6086 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6089 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6092 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6094 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6097 compose_convert_header(compose, buf, sizeof(buf), str,
6098 strlen("Subject: "), FALSE);
6099 g_string_append_printf(header, "Subject: %s\n", buf);
6105 if (compose->account->set_domain && compose->account->domain) {
6106 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6107 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6108 g_snprintf(buf, sizeof(buf), "%s",
6109 strchr(compose->account->address, '@') ?
6110 strchr(compose->account->address, '@')+1 :
6111 compose->account->address);
6113 g_snprintf(buf, sizeof(buf), "%s", "");
6116 if (compose->account->gen_msgid) {
6118 if (compose->account->msgid_with_addr) {
6119 addr = compose->account->address;
6121 generate_msgid(buf, sizeof(buf), addr);
6122 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6123 compose->msgid = g_strdup(buf);
6125 compose->msgid = NULL;
6128 if (compose->remove_references == FALSE) {
6130 if (compose->inreplyto && compose->to_list)
6131 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6134 if (compose->references)
6135 g_string_append_printf(header, "References: %s\n", compose->references);
6139 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6142 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6145 if (compose->account->organization &&
6146 strlen(compose->account->organization) &&
6147 !IS_IN_CUSTOM_HEADER("Organization")) {
6148 compose_convert_header(compose, buf, sizeof(buf),
6149 compose->account->organization,
6150 strlen("Organization: "), FALSE);
6151 g_string_append_printf(header, "Organization: %s\n", buf);
6154 /* Program version and system info */
6155 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6156 !compose->newsgroup_list) {
6157 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6159 gtk_major_version, gtk_minor_version, gtk_micro_version,
6162 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6163 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6165 gtk_major_version, gtk_minor_version, gtk_micro_version,
6169 /* custom headers */
6170 if (compose->account->add_customhdr) {
6173 for (cur = compose->account->customhdr_list; cur != NULL;
6175 CustomHeader *chdr = (CustomHeader *)cur->data;
6177 if (custom_header_is_allowed(chdr->name)) {
6178 compose_convert_header
6179 (compose, buf, sizeof(buf),
6180 chdr->value ? chdr->value : "",
6181 strlen(chdr->name) + 2, FALSE);
6182 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6187 /* Automatic Faces and X-Faces */
6188 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6189 g_string_append_printf(header, "X-Face: %s\n", buf);
6191 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6192 g_string_append_printf(header, "X-Face: %s\n", buf);
6194 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6195 g_string_append_printf(header, "Face: %s\n", buf);
6197 else if (get_default_face (buf, sizeof(buf)) == 0) {
6198 g_string_append_printf(header, "Face: %s\n", buf);
6202 switch (compose->priority) {
6203 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6204 "X-Priority: 1 (Highest)\n");
6206 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6207 "X-Priority: 2 (High)\n");
6209 case PRIORITY_NORMAL: break;
6210 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6211 "X-Priority: 4 (Low)\n");
6213 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6214 "X-Priority: 5 (Lowest)\n");
6216 default: debug_print("compose: priority unknown : %d\n",
6220 /* Request Return Receipt */
6221 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6222 if (compose->return_receipt) {
6223 if (compose->account->name
6224 && *compose->account->name) {
6225 compose_convert_header(compose, buf, sizeof(buf),
6226 compose->account->name,
6227 strlen("Disposition-Notification-To: "),
6229 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6231 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6235 /* get special headers */
6236 for (list = compose->header_list; list; list = list->next) {
6237 ComposeHeaderEntry *headerentry;
6240 gchar *headername_wcolon;
6241 const gchar *headername_trans;
6244 gboolean standard_header = FALSE;
6246 headerentry = ((ComposeHeaderEntry *)list->data);
6248 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6250 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6255 if (!strstr(tmp, ":")) {
6256 headername_wcolon = g_strconcat(tmp, ":", NULL);
6257 headername = g_strdup(tmp);
6259 headername_wcolon = g_strdup(tmp);
6260 headername = g_strdup(strtok(tmp, ":"));
6264 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6265 Xstrdup_a(headervalue, entry_str, return NULL);
6266 subst_char(headervalue, '\r', ' ');
6267 subst_char(headervalue, '\n', ' ');
6268 string = std_headers;
6269 while (*string != NULL) {
6270 headername_trans = prefs_common_translated_header_name(*string);
6271 if (!strcmp(headername_trans, headername_wcolon))
6272 standard_header = TRUE;
6275 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6276 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6279 g_free(headername_wcolon);
6283 g_string_free(header, FALSE);
6288 #undef IS_IN_CUSTOM_HEADER
6290 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6291 gint header_len, gboolean addr_field)
6293 gchar *tmpstr = NULL;
6294 const gchar *out_codeset = NULL;
6296 cm_return_if_fail(src != NULL);
6297 cm_return_if_fail(dest != NULL);
6299 if (len < 1) return;
6301 tmpstr = g_strdup(src);
6303 subst_char(tmpstr, '\n', ' ');
6304 subst_char(tmpstr, '\r', ' ');
6307 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6308 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6309 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6314 codeconv_set_strict(TRUE);
6315 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6316 conv_get_charset_str(compose->out_encoding));
6317 codeconv_set_strict(FALSE);
6319 if (!dest || *dest == '\0') {
6320 gchar *test_conv_global_out = NULL;
6321 gchar *test_conv_reply = NULL;
6323 /* automatic mode. be automatic. */
6324 codeconv_set_strict(TRUE);
6326 out_codeset = conv_get_outgoing_charset_str();
6328 debug_print("trying to convert to %s\n", out_codeset);
6329 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6332 if (!test_conv_global_out && compose->orig_charset
6333 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6334 out_codeset = compose->orig_charset;
6335 debug_print("failure; trying to convert to %s\n", out_codeset);
6336 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6339 if (!test_conv_global_out && !test_conv_reply) {
6341 out_codeset = CS_INTERNAL;
6342 debug_print("finally using %s\n", out_codeset);
6344 g_free(test_conv_global_out);
6345 g_free(test_conv_reply);
6346 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6348 codeconv_set_strict(FALSE);
6353 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6357 cm_return_if_fail(user_data != NULL);
6359 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6360 g_strstrip(address);
6361 if (*address != '\0') {
6362 gchar *name = procheader_get_fromname(address);
6363 extract_address(address);
6364 addressbook_add_contact(name, address, NULL, NULL);
6369 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6371 GtkWidget *menuitem;
6374 cm_return_if_fail(menu != NULL);
6375 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6377 menuitem = gtk_separator_menu_item_new();
6378 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6379 gtk_widget_show(menuitem);
6381 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6382 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6384 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6385 g_strstrip(address);
6386 if (*address == '\0') {
6387 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6390 g_signal_connect(G_OBJECT(menuitem), "activate",
6391 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6392 gtk_widget_show(menuitem);
6395 static void compose_create_header_entry(Compose *compose)
6397 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6404 const gchar *header = NULL;
6405 ComposeHeaderEntry *headerentry;
6406 gboolean standard_header = FALSE;
6407 GtkListStore *model;
6409 #if !(GTK_CHECK_VERSION(2,12,0))
6410 GtkTooltips *tips = compose->tooltips;
6413 headerentry = g_new0(ComposeHeaderEntry, 1);
6416 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6417 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6418 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6420 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6422 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6424 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6425 COMPOSE_NEWSGROUPS);
6426 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6428 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6429 COMPOSE_FOLLOWUPTO);
6431 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6432 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6433 G_CALLBACK(compose_grab_focus_cb), compose);
6434 gtk_widget_show(combo);
6435 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6436 compose->header_nextrow, compose->header_nextrow+1,
6437 GTK_SHRINK, GTK_FILL, 0, 0);
6438 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6439 const gchar *last_header_entry = gtk_entry_get_text(
6440 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6442 while (*string != NULL) {
6443 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6444 standard_header = TRUE;
6447 if (standard_header)
6448 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6450 if (!compose->header_last || !standard_header) {
6451 switch(compose->account->protocol) {
6453 header = prefs_common_translated_header_name("Newsgroups:");
6456 header = prefs_common_translated_header_name("To:");
6461 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6463 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6464 G_CALLBACK(compose_grab_focus_cb), compose);
6466 /* Entry field with cleanup button */
6467 #if GTK_CHECK_VERSION(2, 8, 0)
6468 button = gtk_button_new();
6469 gtk_button_set_image(GTK_BUTTON(button),
6470 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6472 button = gtk_button_new_with_label(_("Clear"));
6474 gtk_widget_show(button);
6475 CLAWS_SET_TIP(button,
6476 _("Delete entry contents"));
6477 entry = gtk_entry_new();
6478 gtk_widget_show(entry);
6479 CLAWS_SET_TIP(entry,
6480 _("Use <tab> to autocomplete from addressbook"));
6481 hbox = gtk_hbox_new (FALSE, 0);
6482 gtk_widget_show(hbox);
6483 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6484 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6485 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6486 compose->header_nextrow, compose->header_nextrow+1,
6487 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6489 g_signal_connect(G_OBJECT(entry), "key-press-event",
6490 G_CALLBACK(compose_headerentry_key_press_event_cb),
6492 g_signal_connect(G_OBJECT(entry), "changed",
6493 G_CALLBACK(compose_headerentry_changed_cb),
6495 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6496 G_CALLBACK(compose_grab_focus_cb), compose);
6498 g_signal_connect(G_OBJECT(button), "clicked",
6499 G_CALLBACK(compose_headerentry_button_clicked_cb),
6503 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6504 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6505 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6506 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6507 G_CALLBACK(compose_header_drag_received_cb),
6509 g_signal_connect(G_OBJECT(entry), "drag-drop",
6510 G_CALLBACK(compose_drag_drop),
6512 g_signal_connect(G_OBJECT(entry), "populate-popup",
6513 G_CALLBACK(compose_entry_popup_extend),
6516 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6518 headerentry->compose = compose;
6519 headerentry->combo = combo;
6520 headerentry->entry = entry;
6521 headerentry->button = button;
6522 headerentry->hbox = hbox;
6523 headerentry->headernum = compose->header_nextrow;
6524 headerentry->type = PREF_NONE;
6526 compose->header_nextrow++;
6527 compose->header_last = headerentry;
6528 compose->header_list =
6529 g_slist_append(compose->header_list,
6533 static void compose_add_header_entry(Compose *compose, const gchar *header,
6534 gchar *text, ComposePrefType pref_type)
6536 ComposeHeaderEntry *last_header = compose->header_last;
6537 gchar *tmp = g_strdup(text), *email;
6539 extract_address(tmp);
6540 email = g_utf8_strdown(tmp, -1);
6542 if (!(!strcmp(header, "To:") && !strcasecmp(compose->account->address, email))
6543 && g_hash_table_lookup(compose->email_hashtable, email) != NULL) {
6544 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6545 header, text, (gint) pref_type);
6551 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6552 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6553 last_header->type = pref_type;
6555 g_hash_table_insert(compose->email_hashtable, email,
6556 GUINT_TO_POINTER(1));
6560 static void compose_destroy_headerentry(Compose *compose,
6561 ComposeHeaderEntry *headerentry)
6563 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6566 extract_address(text);
6567 email = g_utf8_strdown(text, -1);
6568 g_hash_table_remove(compose->email_hashtable, email);
6572 gtk_widget_destroy(headerentry->combo);
6573 gtk_widget_destroy(headerentry->entry);
6574 gtk_widget_destroy(headerentry->button);
6575 gtk_widget_destroy(headerentry->hbox);
6576 g_free(headerentry);
6579 static void compose_remove_header_entries(Compose *compose)
6582 for (list = compose->header_list; list; list = list->next)
6583 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6585 compose->header_last = NULL;
6586 g_slist_free(compose->header_list);
6587 compose->header_list = NULL;
6588 compose->header_nextrow = 1;
6589 compose_create_header_entry(compose);
6592 static GtkWidget *compose_create_header(Compose *compose)
6594 GtkWidget *from_optmenu_hbox;
6595 GtkWidget *header_scrolledwin;
6596 GtkWidget *header_table;
6600 /* header labels and entries */
6601 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6602 gtk_widget_show(header_scrolledwin);
6603 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6605 header_table = gtk_table_new(2, 2, FALSE);
6606 gtk_widget_show(header_table);
6607 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6608 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6609 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6612 /* option menu for selecting accounts */
6613 from_optmenu_hbox = compose_account_option_menu_create(compose);
6614 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6615 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6618 compose->header_table = header_table;
6619 compose->header_list = NULL;
6620 compose->header_nextrow = count;
6622 compose_create_header_entry(compose);
6624 compose->table = NULL;
6626 return header_scrolledwin ;
6629 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6631 Compose *compose = (Compose *)data;
6632 GdkEventButton event;
6635 event.time = gtk_get_current_event_time();
6637 return attach_button_pressed(compose->attach_clist, &event, compose);
6640 static GtkWidget *compose_create_attach(Compose *compose)
6642 GtkWidget *attach_scrwin;
6643 GtkWidget *attach_clist;
6645 GtkListStore *store;
6646 GtkCellRenderer *renderer;
6647 GtkTreeViewColumn *column;
6648 GtkTreeSelection *selection;
6650 /* attachment list */
6651 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6652 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6653 GTK_POLICY_AUTOMATIC,
6654 GTK_POLICY_AUTOMATIC);
6655 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6657 store = gtk_list_store_new(N_ATTACH_COLS,
6662 G_TYPE_AUTO_POINTER,
6664 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6665 (GTK_TREE_MODEL(store)));
6666 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6667 g_object_unref(store);
6669 renderer = gtk_cell_renderer_text_new();
6670 column = gtk_tree_view_column_new_with_attributes
6671 (_("Mime type"), renderer, "text",
6672 COL_MIMETYPE, NULL);
6673 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6675 renderer = gtk_cell_renderer_text_new();
6676 column = gtk_tree_view_column_new_with_attributes
6677 (_("Size"), renderer, "text",
6679 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6681 renderer = gtk_cell_renderer_text_new();
6682 column = gtk_tree_view_column_new_with_attributes
6683 (_("Name"), renderer, "text",
6685 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6687 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6688 prefs_common.use_stripes_everywhere);
6689 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6690 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6692 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6693 G_CALLBACK(attach_selected), compose);
6694 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6695 G_CALLBACK(attach_button_pressed), compose);
6697 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6698 G_CALLBACK(popup_attach_button_pressed), compose);
6700 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6701 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6702 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6703 G_CALLBACK(popup_attach_button_pressed), compose);
6705 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6706 G_CALLBACK(attach_key_pressed), compose);
6709 gtk_drag_dest_set(attach_clist,
6710 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6711 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6712 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6713 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6714 G_CALLBACK(compose_attach_drag_received_cb),
6716 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6717 G_CALLBACK(compose_drag_drop),
6720 compose->attach_scrwin = attach_scrwin;
6721 compose->attach_clist = attach_clist;
6723 return attach_scrwin;
6726 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6727 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6729 static GtkWidget *compose_create_others(Compose *compose)
6732 GtkWidget *savemsg_checkbtn;
6733 GtkWidget *savemsg_combo;
6734 GtkWidget *savemsg_select;
6737 gchar *folderidentifier;
6739 /* Table for settings */
6740 table = gtk_table_new(3, 1, FALSE);
6741 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6742 gtk_widget_show(table);
6743 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6746 /* Save Message to folder */
6747 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6748 gtk_widget_show(savemsg_checkbtn);
6749 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6750 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6751 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6753 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6754 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6756 savemsg_combo = gtk_combo_box_entry_new_text();
6757 compose->savemsg_checkbtn = savemsg_checkbtn;
6758 compose->savemsg_combo = savemsg_combo;
6759 gtk_widget_show(savemsg_combo);
6761 if (prefs_common.compose_save_to_history)
6762 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6763 prefs_common.compose_save_to_history);
6765 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6766 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6767 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6768 G_CALLBACK(compose_grab_focus_cb), compose);
6769 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6770 folderidentifier = folder_item_get_identifier(account_get_special_folder
6771 (compose->account, F_OUTBOX));
6772 compose_set_save_to(compose, folderidentifier);
6773 g_free(folderidentifier);
6776 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6777 gtk_widget_show(savemsg_select);
6778 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6779 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6780 G_CALLBACK(compose_savemsg_select_cb),
6788 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6790 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6791 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6794 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6799 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6802 path = folder_item_get_identifier(dest);
6804 compose_set_save_to(compose, path);
6808 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6809 GdkAtom clip, GtkTextIter *insert_place);
6812 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6816 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6818 if (event->button == 3) {
6820 GtkTextIter sel_start, sel_end;
6821 gboolean stuff_selected;
6823 /* move the cursor to allow GtkAspell to check the word
6824 * under the mouse */
6825 if (event->x && event->y) {
6826 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6827 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6829 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6832 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6833 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6836 stuff_selected = gtk_text_buffer_get_selection_bounds(
6838 &sel_start, &sel_end);
6840 gtk_text_buffer_place_cursor (buffer, &iter);
6841 /* reselect stuff */
6843 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6844 gtk_text_buffer_select_range(buffer,
6845 &sel_start, &sel_end);
6847 return FALSE; /* pass the event so that the right-click goes through */
6850 if (event->button == 2) {
6855 /* get the middle-click position to paste at the correct place */
6856 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6857 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6859 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6862 entry_paste_clipboard(compose, text,
6863 prefs_common.linewrap_pastes,
6864 GDK_SELECTION_PRIMARY, &iter);
6872 static void compose_spell_menu_changed(void *data)
6874 Compose *compose = (Compose *)data;
6876 GtkWidget *menuitem;
6877 GtkWidget *parent_item;
6878 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6881 if (compose->gtkaspell == NULL)
6884 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6885 "/Menu/Spelling/Options");
6887 /* setting the submenu removes /Spelling/Options from the factory
6888 * so we need to save it */
6890 if (parent_item == NULL) {
6891 parent_item = compose->aspell_options_menu;
6892 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6894 compose->aspell_options_menu = parent_item;
6896 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6898 spell_menu = g_slist_reverse(spell_menu);
6899 for (items = spell_menu;
6900 items; items = items->next) {
6901 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6902 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6903 gtk_widget_show(GTK_WIDGET(menuitem));
6905 g_slist_free(spell_menu);
6907 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6908 gtk_widget_show(parent_item);
6911 static void compose_dict_changed(void *data)
6913 Compose *compose = (Compose *) data;
6915 if(compose->gtkaspell &&
6916 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6919 gtkaspell_highlight_all(compose->gtkaspell);
6920 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6924 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6926 Compose *compose = (Compose *)data;
6927 GdkEventButton event;
6930 event.time = gtk_get_current_event_time();
6934 return text_clicked(compose->text, &event, compose);
6937 static gboolean compose_force_window_origin = TRUE;
6938 static Compose *compose_create(PrefsAccount *account,
6947 GtkWidget *handlebox;
6949 GtkWidget *notebook;
6951 GtkWidget *attach_hbox;
6952 GtkWidget *attach_lab1;
6953 GtkWidget *attach_lab2;
6958 GtkWidget *subject_hbox;
6959 GtkWidget *subject_frame;
6960 GtkWidget *subject_entry;
6964 GtkWidget *edit_vbox;
6965 GtkWidget *ruler_hbox;
6967 GtkWidget *scrolledwin;
6969 GtkTextBuffer *buffer;
6970 GtkClipboard *clipboard;
6973 UndoMain *undostruct;
6975 gchar *titles[N_ATTACH_COLS];
6976 GtkWidget *popupmenu;
6977 GtkWidget *tmpl_menu;
6978 GtkActionGroup *action_group = NULL;
6981 GtkAspell * gtkaspell = NULL;
6984 static GdkGeometry geometry;
6986 cm_return_val_if_fail(account != NULL, NULL);
6988 debug_print("Creating compose window...\n");
6989 compose = g_new0(Compose, 1);
6991 titles[COL_MIMETYPE] = _("MIME type");
6992 titles[COL_SIZE] = _("Size");
6993 titles[COL_NAME] = _("Name");
6995 compose->batch = batch;
6996 compose->account = account;
6997 compose->folder = folder;
6999 compose->mutex = g_mutex_new();
7000 compose->set_cursor_pos = -1;
7002 #if !(GTK_CHECK_VERSION(2,12,0))
7003 compose->tooltips = tips;
7006 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7008 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7009 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7011 if (!geometry.max_width) {
7012 geometry.max_width = gdk_screen_width();
7013 geometry.max_height = gdk_screen_height();
7016 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7017 &geometry, GDK_HINT_MAX_SIZE);
7018 if (!geometry.min_width) {
7019 geometry.min_width = 600;
7020 geometry.min_height = 440;
7022 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7023 &geometry, GDK_HINT_MIN_SIZE);
7025 #ifndef GENERIC_UMPC
7026 if (compose_force_window_origin)
7027 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7028 prefs_common.compose_y);
7030 g_signal_connect(G_OBJECT(window), "delete_event",
7031 G_CALLBACK(compose_delete_cb), compose);
7032 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7033 gtk_widget_realize(window);
7035 gtkut_widget_set_composer_icon(window);
7037 vbox = gtk_vbox_new(FALSE, 0);
7038 gtk_container_add(GTK_CONTAINER(window), vbox);
7040 compose->ui_manager = gtk_ui_manager_new();
7041 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7042 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7043 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7044 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7045 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7046 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7047 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7048 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7049 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7050 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7053 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7055 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7058 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7059 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7061 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7063 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7064 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7065 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7068 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7069 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7070 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7071 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7072 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7073 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7074 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7075 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7076 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7077 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7080 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7081 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7082 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7084 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7085 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7086 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7088 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7089 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7090 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7091 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7093 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7095 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7096 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7097 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7098 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7099 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7100 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7103 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7105 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7106 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7107 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7111 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7116 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7125 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7135 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7136 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7137 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7144 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7152 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7158 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7159 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7167 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7170 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)
7171 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)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7177 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)
7178 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)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7182 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7183 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)
7184 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7186 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7187 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)
7188 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7190 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7192 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7193 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)
7194 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7196 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7198 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7199 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)
7200 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)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7202 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7204 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7206 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7210 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7212 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)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7216 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7222 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7225 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7228 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7230 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7231 gtk_widget_show_all(menubar);
7233 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7235 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7237 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7240 if (prefs_common.toolbar_detachable) {
7241 handlebox = gtk_handle_box_new();
7243 handlebox = gtk_hbox_new(FALSE, 0);
7245 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7247 gtk_widget_realize(handlebox);
7249 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7252 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7256 vbox2 = gtk_vbox_new(FALSE, 2);
7257 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7258 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7261 notebook = gtk_notebook_new();
7262 gtk_widget_set_size_request(notebook, -1, 130);
7263 gtk_widget_show(notebook);
7265 /* header labels and entries */
7266 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7267 compose_create_header(compose),
7268 gtk_label_new_with_mnemonic(_("Hea_der")));
7269 /* attachment list */
7270 attach_hbox = gtk_hbox_new(FALSE, 0);
7271 gtk_widget_show(attach_hbox);
7273 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7274 gtk_widget_show(attach_lab1);
7275 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7277 attach_lab2 = gtk_label_new("");
7278 gtk_widget_show(attach_lab2);
7279 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7281 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7282 compose_create_attach(compose),
7285 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7286 compose_create_others(compose),
7287 gtk_label_new_with_mnemonic(_("Othe_rs")));
7290 subject_hbox = gtk_hbox_new(FALSE, 0);
7291 gtk_widget_show(subject_hbox);
7293 subject_frame = gtk_frame_new(NULL);
7294 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7295 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7296 gtk_widget_show(subject_frame);
7298 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7299 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7300 gtk_widget_show(subject);
7302 label = gtk_label_new(_("Subject:"));
7303 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7304 gtk_widget_show(label);
7307 subject_entry = claws_spell_entry_new();
7309 subject_entry = gtk_entry_new();
7311 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7312 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7313 G_CALLBACK(compose_grab_focus_cb), compose);
7314 gtk_widget_show(subject_entry);
7315 compose->subject_entry = subject_entry;
7316 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7318 edit_vbox = gtk_vbox_new(FALSE, 0);
7320 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7323 ruler_hbox = gtk_hbox_new(FALSE, 0);
7324 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7326 ruler = gtk_shruler_new();
7327 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7328 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7332 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7333 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7334 GTK_POLICY_AUTOMATIC,
7335 GTK_POLICY_AUTOMATIC);
7336 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7338 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7339 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7341 text = gtk_text_view_new();
7342 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7343 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7344 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7345 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7346 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7348 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7350 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7351 G_CALLBACK(compose_edit_size_alloc),
7353 g_signal_connect(G_OBJECT(buffer), "changed",
7354 G_CALLBACK(compose_changed_cb), compose);
7355 g_signal_connect(G_OBJECT(text), "grab_focus",
7356 G_CALLBACK(compose_grab_focus_cb), compose);
7357 g_signal_connect(G_OBJECT(buffer), "insert_text",
7358 G_CALLBACK(text_inserted), compose);
7359 g_signal_connect(G_OBJECT(text), "button_press_event",
7360 G_CALLBACK(text_clicked), compose);
7362 g_signal_connect(G_OBJECT(text), "popup-menu",
7363 G_CALLBACK(compose_popup_menu), compose);
7365 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7366 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7367 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7368 G_CALLBACK(compose_popup_menu), compose);
7370 g_signal_connect(G_OBJECT(subject_entry), "changed",
7371 G_CALLBACK(compose_changed_cb), compose);
7374 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7375 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7376 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7377 g_signal_connect(G_OBJECT(text), "drag_data_received",
7378 G_CALLBACK(compose_insert_drag_received_cb),
7380 g_signal_connect(G_OBJECT(text), "drag-drop",
7381 G_CALLBACK(compose_drag_drop),
7383 gtk_widget_show_all(vbox);
7385 /* pane between attach clist and text */
7386 paned = gtk_vpaned_new();
7387 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7389 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7390 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7392 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7394 gtk_paned_add1(GTK_PANED(paned), notebook);
7395 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7396 gtk_widget_show_all(paned);
7399 if (prefs_common.textfont) {
7400 PangoFontDescription *font_desc;
7402 font_desc = pango_font_description_from_string
7403 (prefs_common.textfont);
7405 gtk_widget_modify_font(text, font_desc);
7406 pango_font_description_free(font_desc);
7410 gtk_action_group_add_actions(action_group, compose_popup_entries,
7411 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7414 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7416 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7417 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7419 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7421 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7422 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7423 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7425 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7427 undostruct = undo_init(text);
7428 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7431 address_completion_start(window);
7433 compose->window = window;
7434 compose->vbox = vbox;
7435 compose->menubar = menubar;
7436 compose->handlebox = handlebox;
7438 compose->vbox2 = vbox2;
7440 compose->paned = paned;
7442 compose->attach_label = attach_lab2;
7444 compose->notebook = notebook;
7445 compose->edit_vbox = edit_vbox;
7446 compose->ruler_hbox = ruler_hbox;
7447 compose->ruler = ruler;
7448 compose->scrolledwin = scrolledwin;
7449 compose->text = text;
7451 compose->focused_editable = NULL;
7453 compose->popupmenu = popupmenu;
7455 compose->tmpl_menu = tmpl_menu;
7457 compose->mode = mode;
7458 compose->rmode = mode;
7460 compose->targetinfo = NULL;
7461 compose->replyinfo = NULL;
7462 compose->fwdinfo = NULL;
7464 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7465 g_str_equal, (GDestroyNotify) g_free, NULL);
7467 compose->replyto = NULL;
7469 compose->bcc = NULL;
7470 compose->followup_to = NULL;
7472 compose->ml_post = NULL;
7474 compose->inreplyto = NULL;
7475 compose->references = NULL;
7476 compose->msgid = NULL;
7477 compose->boundary = NULL;
7479 compose->autowrap = prefs_common.autowrap;
7480 compose->autoindent = prefs_common.auto_indent;
7481 compose->use_signing = FALSE;
7482 compose->use_encryption = FALSE;
7483 compose->privacy_system = NULL;
7485 compose->modified = FALSE;
7487 compose->return_receipt = FALSE;
7489 compose->to_list = NULL;
7490 compose->newsgroup_list = NULL;
7492 compose->undostruct = undostruct;
7494 compose->sig_str = NULL;
7496 compose->exteditor_file = NULL;
7497 compose->exteditor_pid = -1;
7498 compose->exteditor_tag = -1;
7499 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7502 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7503 if (mode != COMPOSE_REDIRECT) {
7504 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7505 strcmp(prefs_common.dictionary, "")) {
7506 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7507 prefs_common.alt_dictionary,
7508 conv_get_locale_charset_str(),
7509 prefs_common.misspelled_col,
7510 prefs_common.check_while_typing,
7511 prefs_common.recheck_when_changing_dict,
7512 prefs_common.use_alternate,
7513 prefs_common.use_both_dicts,
7514 GTK_TEXT_VIEW(text),
7515 GTK_WINDOW(compose->window),
7516 compose_dict_changed,
7517 compose_spell_menu_changed,
7520 alertpanel_error(_("Spell checker could not "
7522 gtkaspell_checkers_strerror());
7523 gtkaspell_checkers_reset_error();
7525 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7529 compose_spell_menu_changed(compose);
7530 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7531 compose->gtkaspell = gtkaspell;
7534 compose_select_account(compose, account, TRUE);
7536 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7537 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7539 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7540 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7542 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7543 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7545 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7546 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7548 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7549 if (account->protocol != A_NNTP)
7550 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7551 prefs_common_translated_header_name("To:"));
7553 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7554 prefs_common_translated_header_name("Newsgroups:"));
7556 addressbook_set_target_compose(compose);
7558 if (mode != COMPOSE_REDIRECT)
7559 compose_set_template_menu(compose);
7561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7564 compose_list = g_list_append(compose_list, compose);
7566 if (!prefs_common.show_ruler)
7567 gtk_widget_hide(ruler_hbox);
7569 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7572 compose->priority = PRIORITY_NORMAL;
7573 compose_update_priority_menu_item(compose);
7575 compose_set_out_encoding(compose);
7578 compose_update_actions_menu(compose);
7580 /* Privacy Systems menu */
7581 compose_update_privacy_systems_menu(compose);
7583 activate_privacy_system(compose, account, TRUE);
7584 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7586 gtk_widget_realize(window);
7588 gtk_widget_show(window);
7590 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7591 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7598 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7603 GtkWidget *optmenubox;
7606 GtkWidget *from_name = NULL;
7607 #if !(GTK_CHECK_VERSION(2,12,0))
7608 GtkTooltips *tips = compose->tooltips;
7611 gint num = 0, def_menu = 0;
7613 accounts = account_get_list();
7614 cm_return_val_if_fail(accounts != NULL, NULL);
7616 optmenubox = gtk_event_box_new();
7617 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7618 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7620 hbox = gtk_hbox_new(FALSE, 6);
7621 from_name = gtk_entry_new();
7623 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7624 G_CALLBACK(compose_grab_focus_cb), compose);
7626 for (; accounts != NULL; accounts = accounts->next, num++) {
7627 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7628 gchar *name, *from = NULL;
7630 if (ac == compose->account) def_menu = num;
7632 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7635 if (ac == compose->account) {
7636 if (ac->name && *ac->name) {
7638 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7639 from = g_strdup_printf("%s <%s>",
7641 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7643 from = g_strdup_printf("%s",
7645 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7648 COMBOBOX_ADD(menu, name, ac->account_id);
7653 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7655 g_signal_connect(G_OBJECT(optmenu), "changed",
7656 G_CALLBACK(account_activated),
7658 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7659 G_CALLBACK(compose_entry_popup_extend),
7662 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7663 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7665 CLAWS_SET_TIP(optmenubox,
7666 _("Account to use for this email"));
7667 CLAWS_SET_TIP(from_name,
7668 _("Sender address to be used"));
7670 compose->from_name = from_name;
7675 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7677 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7678 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7679 Compose *compose = (Compose *) data;
7681 compose->priority = value;
7685 static void compose_reply_change_mode(Compose *compose,
7688 gboolean was_modified = compose->modified;
7690 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7692 cm_return_if_fail(compose->replyinfo != NULL);
7694 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7696 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7698 if (action == COMPOSE_REPLY_TO_ALL)
7700 if (action == COMPOSE_REPLY_TO_SENDER)
7702 if (action == COMPOSE_REPLY_TO_LIST)
7705 compose_remove_header_entries(compose);
7706 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7707 if (compose->account->set_autocc && compose->account->auto_cc)
7708 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7710 if (compose->account->set_autobcc && compose->account->auto_bcc)
7711 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7713 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7714 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7715 compose_show_first_last_header(compose, TRUE);
7716 compose->modified = was_modified;
7717 compose_set_title(compose);
7720 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7722 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7723 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7724 Compose *compose = (Compose *) data;
7727 compose_reply_change_mode(compose, value);
7730 static void compose_update_priority_menu_item(Compose * compose)
7732 GtkWidget *menuitem = NULL;
7733 switch (compose->priority) {
7734 case PRIORITY_HIGHEST:
7735 menuitem = gtk_ui_manager_get_widget
7736 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7739 menuitem = gtk_ui_manager_get_widget
7740 (compose->ui_manager, "/Menu/Options/Priority/High");
7742 case PRIORITY_NORMAL:
7743 menuitem = gtk_ui_manager_get_widget
7744 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7747 menuitem = gtk_ui_manager_get_widget
7748 (compose->ui_manager, "/Menu/Options/Priority/Low");
7750 case PRIORITY_LOWEST:
7751 menuitem = gtk_ui_manager_get_widget
7752 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7755 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7758 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7760 Compose *compose = (Compose *) data;
7762 gboolean can_sign = FALSE, can_encrypt = FALSE;
7764 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7766 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7769 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7770 g_free(compose->privacy_system);
7771 compose->privacy_system = NULL;
7772 if (systemid != NULL) {
7773 compose->privacy_system = g_strdup(systemid);
7775 can_sign = privacy_system_can_sign(systemid);
7776 can_encrypt = privacy_system_can_encrypt(systemid);
7779 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7781 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7782 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7785 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7787 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7788 GtkWidget *menuitem = NULL;
7790 gboolean can_sign = FALSE, can_encrypt = FALSE;
7791 gboolean found = FALSE;
7793 if (compose->privacy_system != NULL) {
7795 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7796 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7797 cm_return_if_fail(menuitem != NULL);
7799 amenu = GTK_MENU_SHELL(menuitem)->children;
7801 while (amenu != NULL) {
7802 GList *alist = amenu->next;
7804 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7805 if (systemid != NULL) {
7806 if (strcmp(systemid, compose->privacy_system) == 0 &&
7807 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7808 menuitem = GTK_WIDGET(amenu->data);
7810 can_sign = privacy_system_can_sign(systemid);
7811 can_encrypt = privacy_system_can_encrypt(systemid);
7815 } else if (strlen(compose->privacy_system) == 0 &&
7816 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7817 menuitem = GTK_WIDGET(amenu->data);
7820 can_encrypt = FALSE;
7827 if (menuitem != NULL)
7828 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7830 if (warn && !found && strlen(compose->privacy_system)) {
7831 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7832 "will not be able to sign or encrypt this message."),
7833 compose->privacy_system);
7837 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7838 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7841 static void compose_set_out_encoding(Compose *compose)
7843 CharSet out_encoding;
7844 const gchar *branch = NULL;
7845 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7847 switch(out_encoding) {
7848 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7849 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7850 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7851 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7852 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7853 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7854 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7855 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7856 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7857 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7858 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7859 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7860 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7861 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7862 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7863 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7864 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7865 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7866 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7867 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7868 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7869 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7870 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7871 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7872 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7873 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7874 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7875 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7876 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7877 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7878 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7879 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7881 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7884 static void compose_set_template_menu(Compose *compose)
7886 GSList *tmpl_list, *cur;
7890 tmpl_list = template_get_config();
7892 menu = gtk_menu_new();
7894 gtk_menu_set_accel_group (GTK_MENU (menu),
7895 gtk_ui_manager_get_accel_group(compose->ui_manager));
7896 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7897 Template *tmpl = (Template *)cur->data;
7898 gchar *accel_path = NULL;
7899 item = gtk_menu_item_new_with_label(tmpl->name);
7900 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7901 g_signal_connect(G_OBJECT(item), "activate",
7902 G_CALLBACK(compose_template_activate_cb),
7904 g_object_set_data(G_OBJECT(item), "template", tmpl);
7905 gtk_widget_show(item);
7906 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7907 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7911 gtk_widget_show(menu);
7912 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7915 void compose_update_actions_menu(Compose *compose)
7917 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7920 static void compose_update_privacy_systems_menu(Compose *compose)
7922 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7923 GSList *systems, *cur;
7925 GtkWidget *system_none;
7927 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7928 GtkWidget *privacy_menu = gtk_menu_new();
7930 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7931 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7933 g_signal_connect(G_OBJECT(system_none), "activate",
7934 G_CALLBACK(compose_set_privacy_system_cb), compose);
7936 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7937 gtk_widget_show(system_none);
7939 systems = privacy_get_system_ids();
7940 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7941 gchar *systemid = cur->data;
7943 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7944 widget = gtk_radio_menu_item_new_with_label(group,
7945 privacy_system_get_name(systemid));
7946 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7947 g_strdup(systemid), g_free);
7948 g_signal_connect(G_OBJECT(widget), "activate",
7949 G_CALLBACK(compose_set_privacy_system_cb), compose);
7951 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
7952 gtk_widget_show(widget);
7955 g_slist_free(systems);
7956 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
7957 gtk_widget_show_all(privacy_menu);
7958 gtk_widget_show_all(privacy_menuitem);
7961 void compose_reflect_prefs_all(void)
7966 for (cur = compose_list; cur != NULL; cur = cur->next) {
7967 compose = (Compose *)cur->data;
7968 compose_set_template_menu(compose);
7972 void compose_reflect_prefs_pixmap_theme(void)
7977 for (cur = compose_list; cur != NULL; cur = cur->next) {
7978 compose = (Compose *)cur->data;
7979 toolbar_update(TOOLBAR_COMPOSE, compose);
7983 static const gchar *compose_quote_char_from_context(Compose *compose)
7985 const gchar *qmark = NULL;
7987 cm_return_val_if_fail(compose != NULL, NULL);
7989 switch (compose->mode) {
7990 /* use forward-specific quote char */
7991 case COMPOSE_FORWARD:
7992 case COMPOSE_FORWARD_AS_ATTACH:
7993 case COMPOSE_FORWARD_INLINE:
7994 if (compose->folder && compose->folder->prefs &&
7995 compose->folder->prefs->forward_with_format)
7996 qmark = compose->folder->prefs->forward_quotemark;
7997 else if (compose->account->forward_with_format)
7998 qmark = compose->account->forward_quotemark;
8000 qmark = prefs_common.fw_quotemark;
8003 /* use reply-specific quote char in all other modes */
8005 if (compose->folder && compose->folder->prefs &&
8006 compose->folder->prefs->reply_with_format)
8007 qmark = compose->folder->prefs->reply_quotemark;
8008 else if (compose->account->reply_with_format)
8009 qmark = compose->account->reply_quotemark;
8011 qmark = prefs_common.quotemark;
8015 if (qmark == NULL || *qmark == '\0')
8021 static void compose_template_apply(Compose *compose, Template *tmpl,
8025 GtkTextBuffer *buffer;
8029 gchar *parsed_str = NULL;
8030 gint cursor_pos = 0;
8031 const gchar *err_msg = _("The body of the template has an error at line %d.");
8034 /* process the body */
8036 text = GTK_TEXT_VIEW(compose->text);
8037 buffer = gtk_text_view_get_buffer(text);
8040 qmark = compose_quote_char_from_context(compose);
8042 if (compose->replyinfo != NULL) {
8045 gtk_text_buffer_set_text(buffer, "", -1);
8046 mark = gtk_text_buffer_get_insert(buffer);
8047 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8049 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8050 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8052 } else if (compose->fwdinfo != NULL) {
8055 gtk_text_buffer_set_text(buffer, "", -1);
8056 mark = gtk_text_buffer_get_insert(buffer);
8057 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8059 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8060 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8063 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8065 GtkTextIter start, end;
8068 gtk_text_buffer_get_start_iter(buffer, &start);
8069 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8070 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8072 /* clear the buffer now */
8074 gtk_text_buffer_set_text(buffer, "", -1);
8076 parsed_str = compose_quote_fmt(compose, dummyinfo,
8077 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8078 procmsg_msginfo_free( dummyinfo );
8084 gtk_text_buffer_set_text(buffer, "", -1);
8085 mark = gtk_text_buffer_get_insert(buffer);
8086 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8089 if (replace && parsed_str && compose->account->auto_sig)
8090 compose_insert_sig(compose, FALSE);
8092 if (replace && parsed_str) {
8093 gtk_text_buffer_get_start_iter(buffer, &iter);
8094 gtk_text_buffer_place_cursor(buffer, &iter);
8098 cursor_pos = quote_fmt_get_cursor_pos();
8099 compose->set_cursor_pos = cursor_pos;
8100 if (cursor_pos == -1)
8102 gtk_text_buffer_get_start_iter(buffer, &iter);
8103 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8104 gtk_text_buffer_place_cursor(buffer, &iter);
8107 /* process the other fields */
8109 compose_template_apply_fields(compose, tmpl);
8110 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8111 quote_fmt_reset_vartable();
8112 compose_changed_cb(NULL, compose);
8115 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8116 gtkaspell_highlight_all(compose->gtkaspell);
8120 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8122 MsgInfo* dummyinfo = NULL;
8123 MsgInfo *msginfo = NULL;
8126 if (compose->replyinfo != NULL)
8127 msginfo = compose->replyinfo;
8128 else if (compose->fwdinfo != NULL)
8129 msginfo = compose->fwdinfo;
8131 dummyinfo = compose_msginfo_new_from_compose(compose);
8132 msginfo = dummyinfo;
8135 if (tmpl->from && *tmpl->from != '\0') {
8137 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8138 compose->gtkaspell);
8140 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8142 quote_fmt_scan_string(tmpl->from);
8145 buf = quote_fmt_get_buffer();
8147 alertpanel_error(_("Template From format error."));
8149 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8153 if (tmpl->to && *tmpl->to != '\0') {
8155 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8156 compose->gtkaspell);
8158 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8160 quote_fmt_scan_string(tmpl->to);
8163 buf = quote_fmt_get_buffer();
8165 alertpanel_error(_("Template To format error."));
8167 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8171 if (tmpl->cc && *tmpl->cc != '\0') {
8173 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8174 compose->gtkaspell);
8176 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8178 quote_fmt_scan_string(tmpl->cc);
8181 buf = quote_fmt_get_buffer();
8183 alertpanel_error(_("Template Cc format error."));
8185 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8189 if (tmpl->bcc && *tmpl->bcc != '\0') {
8191 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8192 compose->gtkaspell);
8194 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8196 quote_fmt_scan_string(tmpl->bcc);
8199 buf = quote_fmt_get_buffer();
8201 alertpanel_error(_("Template Bcc format error."));
8203 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8207 /* process the subject */
8208 if (tmpl->subject && *tmpl->subject != '\0') {
8210 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8211 compose->gtkaspell);
8213 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8215 quote_fmt_scan_string(tmpl->subject);
8218 buf = quote_fmt_get_buffer();
8220 alertpanel_error(_("Template subject format error."));
8222 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8226 procmsg_msginfo_free( dummyinfo );
8229 static void compose_destroy(Compose *compose)
8231 GtkTextBuffer *buffer;
8232 GtkClipboard *clipboard;
8234 compose_list = g_list_remove(compose_list, compose);
8236 if (compose->updating) {
8237 debug_print("danger, not destroying anything now\n");
8238 compose->deferred_destroy = TRUE;
8241 /* NOTE: address_completion_end() does nothing with the window
8242 * however this may change. */
8243 address_completion_end(compose->window);
8245 slist_free_strings(compose->to_list);
8246 g_slist_free(compose->to_list);
8247 slist_free_strings(compose->newsgroup_list);
8248 g_slist_free(compose->newsgroup_list);
8249 slist_free_strings(compose->header_list);
8250 g_slist_free(compose->header_list);
8252 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8254 g_hash_table_destroy(compose->email_hashtable);
8256 procmsg_msginfo_free(compose->targetinfo);
8257 procmsg_msginfo_free(compose->replyinfo);
8258 procmsg_msginfo_free(compose->fwdinfo);
8260 g_free(compose->replyto);
8261 g_free(compose->cc);
8262 g_free(compose->bcc);
8263 g_free(compose->newsgroups);
8264 g_free(compose->followup_to);
8266 g_free(compose->ml_post);
8268 g_free(compose->inreplyto);
8269 g_free(compose->references);
8270 g_free(compose->msgid);
8271 g_free(compose->boundary);
8273 g_free(compose->redirect_filename);
8274 if (compose->undostruct)
8275 undo_destroy(compose->undostruct);
8277 g_free(compose->sig_str);
8279 g_free(compose->exteditor_file);
8281 g_free(compose->orig_charset);
8283 g_free(compose->privacy_system);
8285 if (addressbook_get_target_compose() == compose)
8286 addressbook_set_target_compose(NULL);
8289 if (compose->gtkaspell) {
8290 gtkaspell_delete(compose->gtkaspell);
8291 compose->gtkaspell = NULL;
8295 if (!compose->batch) {
8296 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8297 prefs_common.compose_height = compose->window->allocation.height;
8300 if (!gtk_widget_get_parent(compose->paned))
8301 gtk_widget_destroy(compose->paned);
8302 gtk_widget_destroy(compose->popupmenu);
8304 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8305 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8306 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8308 gtk_widget_destroy(compose->window);
8309 toolbar_destroy(compose->toolbar);
8310 g_free(compose->toolbar);
8311 g_mutex_free(compose->mutex);
8315 static void compose_attach_info_free(AttachInfo *ainfo)
8317 g_free(ainfo->file);
8318 g_free(ainfo->content_type);
8319 g_free(ainfo->name);
8323 static void compose_attach_update_label(Compose *compose)
8328 GtkTreeModel *model;
8333 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8334 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8335 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8339 while(gtk_tree_model_iter_next(model, &iter))
8342 text = g_strdup_printf("(%d)", i);
8343 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8347 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8349 Compose *compose = (Compose *)data;
8350 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8351 GtkTreeSelection *selection;
8353 GtkTreeModel *model;
8355 selection = gtk_tree_view_get_selection(tree_view);
8356 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8361 for (cur = sel; cur != NULL; cur = cur->next) {
8362 GtkTreePath *path = cur->data;
8363 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8366 gtk_tree_path_free(path);
8369 for (cur = sel; cur != NULL; cur = cur->next) {
8370 GtkTreeRowReference *ref = cur->data;
8371 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8374 if (gtk_tree_model_get_iter(model, &iter, path))
8375 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8377 gtk_tree_path_free(path);
8378 gtk_tree_row_reference_free(ref);
8382 compose_attach_update_label(compose);
8385 static struct _AttachProperty
8388 GtkWidget *mimetype_entry;
8389 GtkWidget *encoding_optmenu;
8390 GtkWidget *path_entry;
8391 GtkWidget *filename_entry;
8393 GtkWidget *cancel_btn;
8396 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8398 gtk_tree_path_free((GtkTreePath *)ptr);
8401 static void compose_attach_property(GtkAction *action, gpointer data)
8403 Compose *compose = (Compose *)data;
8404 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8406 GtkComboBox *optmenu;
8407 GtkTreeSelection *selection;
8409 GtkTreeModel *model;
8412 static gboolean cancelled;
8414 /* only if one selected */
8415 selection = gtk_tree_view_get_selection(tree_view);
8416 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8419 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8423 path = (GtkTreePath *) sel->data;
8424 gtk_tree_model_get_iter(model, &iter, path);
8425 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8428 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8434 if (!attach_prop.window)
8435 compose_attach_property_create(&cancelled);
8436 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8437 gtk_widget_grab_focus(attach_prop.ok_btn);
8438 gtk_widget_show(attach_prop.window);
8439 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8441 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8442 if (ainfo->encoding == ENC_UNKNOWN)
8443 combobox_select_by_data(optmenu, ENC_BASE64);
8445 combobox_select_by_data(optmenu, ainfo->encoding);
8447 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8448 ainfo->content_type ? ainfo->content_type : "");
8449 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8450 ainfo->file ? ainfo->file : "");
8451 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8452 ainfo->name ? ainfo->name : "");
8455 const gchar *entry_text;
8457 gchar *cnttype = NULL;
8464 gtk_widget_hide(attach_prop.window);
8465 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8470 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8471 if (*entry_text != '\0') {
8474 text = g_strstrip(g_strdup(entry_text));
8475 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8476 cnttype = g_strdup(text);
8479 alertpanel_error(_("Invalid MIME type."));
8485 ainfo->encoding = combobox_get_active_data(optmenu);
8487 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8488 if (*entry_text != '\0') {
8489 if (is_file_exist(entry_text) &&
8490 (size = get_file_size(entry_text)) > 0)
8491 file = g_strdup(entry_text);
8494 (_("File doesn't exist or is empty."));
8500 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8501 if (*entry_text != '\0') {
8502 g_free(ainfo->name);
8503 ainfo->name = g_strdup(entry_text);
8507 g_free(ainfo->content_type);
8508 ainfo->content_type = cnttype;
8511 g_free(ainfo->file);
8515 ainfo->size = (goffset)size;
8517 /* update tree store */
8518 text = to_human_readable(ainfo->size);
8519 gtk_tree_model_get_iter(model, &iter, path);
8520 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8521 COL_MIMETYPE, ainfo->content_type,
8523 COL_NAME, ainfo->name,
8529 gtk_tree_path_free(path);
8532 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8534 label = gtk_label_new(str); \
8535 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8536 GTK_FILL, 0, 0, 0); \
8537 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8539 entry = gtk_entry_new(); \
8540 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8541 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8544 static void compose_attach_property_create(gboolean *cancelled)
8550 GtkWidget *mimetype_entry;
8553 GtkListStore *optmenu_menu;
8554 GtkWidget *path_entry;
8555 GtkWidget *filename_entry;
8558 GtkWidget *cancel_btn;
8559 GList *mime_type_list, *strlist;
8562 debug_print("Creating attach_property window...\n");
8564 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8565 gtk_widget_set_size_request(window, 480, -1);
8566 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8567 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8568 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8569 g_signal_connect(G_OBJECT(window), "delete_event",
8570 G_CALLBACK(attach_property_delete_event),
8572 g_signal_connect(G_OBJECT(window), "key_press_event",
8573 G_CALLBACK(attach_property_key_pressed),
8576 vbox = gtk_vbox_new(FALSE, 8);
8577 gtk_container_add(GTK_CONTAINER(window), vbox);
8579 table = gtk_table_new(4, 2, FALSE);
8580 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8581 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8582 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8584 label = gtk_label_new(_("MIME type"));
8585 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8587 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8588 mimetype_entry = gtk_combo_box_entry_new_text();
8589 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8590 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8592 /* stuff with list */
8593 mime_type_list = procmime_get_mime_type_list();
8595 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8596 MimeType *type = (MimeType *) mime_type_list->data;
8599 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8601 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8604 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8605 (GCompareFunc)strcmp2);
8608 for (mime_type_list = strlist; mime_type_list != NULL;
8609 mime_type_list = mime_type_list->next) {
8610 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8611 g_free(mime_type_list->data);
8613 g_list_free(strlist);
8614 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8615 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8617 label = gtk_label_new(_("Encoding"));
8618 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8620 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8622 hbox = gtk_hbox_new(FALSE, 0);
8623 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8624 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8626 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8627 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8629 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8630 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8631 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8632 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8633 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8635 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8637 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8638 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8640 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8641 &ok_btn, GTK_STOCK_OK,
8643 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8644 gtk_widget_grab_default(ok_btn);
8646 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8647 G_CALLBACK(attach_property_ok),
8649 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8650 G_CALLBACK(attach_property_cancel),
8653 gtk_widget_show_all(vbox);
8655 attach_prop.window = window;
8656 attach_prop.mimetype_entry = mimetype_entry;
8657 attach_prop.encoding_optmenu = optmenu;
8658 attach_prop.path_entry = path_entry;
8659 attach_prop.filename_entry = filename_entry;
8660 attach_prop.ok_btn = ok_btn;
8661 attach_prop.cancel_btn = cancel_btn;
8664 #undef SET_LABEL_AND_ENTRY
8666 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8672 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8678 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8679 gboolean *cancelled)
8687 static gboolean attach_property_key_pressed(GtkWidget *widget,
8689 gboolean *cancelled)
8691 if (event && event->keyval == GDK_Escape) {
8695 if (event && event->keyval == GDK_Return) {
8703 static void compose_exec_ext_editor(Compose *compose)
8710 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8711 G_DIR_SEPARATOR, compose);
8713 if (pipe(pipe_fds) < 0) {
8719 if ((pid = fork()) < 0) {
8726 /* close the write side of the pipe */
8729 compose->exteditor_file = g_strdup(tmp);
8730 compose->exteditor_pid = pid;
8732 compose_set_ext_editor_sensitive(compose, FALSE);
8735 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8737 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8739 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8743 } else { /* process-monitoring process */
8749 /* close the read side of the pipe */
8752 if (compose_write_body_to_file(compose, tmp) < 0) {
8753 fd_write_all(pipe_fds[1], "2\n", 2);
8757 pid_ed = compose_exec_ext_editor_real(tmp);
8759 fd_write_all(pipe_fds[1], "1\n", 2);
8763 /* wait until editor is terminated */
8764 waitpid(pid_ed, NULL, 0);
8766 fd_write_all(pipe_fds[1], "0\n", 2);
8773 #endif /* G_OS_UNIX */
8777 static gint compose_exec_ext_editor_real(const gchar *file)
8784 cm_return_val_if_fail(file != NULL, -1);
8786 if ((pid = fork()) < 0) {
8791 if (pid != 0) return pid;
8793 /* grandchild process */
8795 if (setpgid(0, getppid()))
8798 if (prefs_common_get_ext_editor_cmd() &&
8799 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8800 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8801 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8803 if (prefs_common_get_ext_editor_cmd())
8804 g_warning("External editor command-line is invalid: '%s'\n",
8805 prefs_common_get_ext_editor_cmd());
8806 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8809 cmdline = strsplit_with_quote(buf, " ", 1024);
8810 execvp(cmdline[0], cmdline);
8813 g_strfreev(cmdline);
8818 static gboolean compose_ext_editor_kill(Compose *compose)
8820 pid_t pgid = compose->exteditor_pid * -1;
8823 ret = kill(pgid, 0);
8825 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8829 msg = g_strdup_printf
8830 (_("The external editor is still working.\n"
8831 "Force terminating the process?\n"
8832 "process group id: %d"), -pgid);
8833 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8834 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8838 if (val == G_ALERTALTERNATE) {
8839 g_source_remove(compose->exteditor_tag);
8840 g_io_channel_shutdown(compose->exteditor_ch,
8842 g_io_channel_unref(compose->exteditor_ch);
8844 if (kill(pgid, SIGTERM) < 0) perror("kill");
8845 waitpid(compose->exteditor_pid, NULL, 0);
8847 g_warning("Terminated process group id: %d", -pgid);
8848 g_warning("Temporary file: %s",
8849 compose->exteditor_file);
8851 compose_set_ext_editor_sensitive(compose, TRUE);
8853 g_free(compose->exteditor_file);
8854 compose->exteditor_file = NULL;
8855 compose->exteditor_pid = -1;
8856 compose->exteditor_ch = NULL;
8857 compose->exteditor_tag = -1;
8865 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8869 Compose *compose = (Compose *)data;
8872 debug_print(_("Compose: input from monitoring process\n"));
8874 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8876 g_io_channel_shutdown(source, FALSE, NULL);
8877 g_io_channel_unref(source);
8879 waitpid(compose->exteditor_pid, NULL, 0);
8881 if (buf[0] == '0') { /* success */
8882 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8883 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8885 gtk_text_buffer_set_text(buffer, "", -1);
8886 compose_insert_file(compose, compose->exteditor_file);
8887 compose_changed_cb(NULL, compose);
8888 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8890 if (claws_unlink(compose->exteditor_file) < 0)
8891 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8892 } else if (buf[0] == '1') { /* failed */
8893 g_warning("Couldn't exec external editor\n");
8894 if (claws_unlink(compose->exteditor_file) < 0)
8895 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8896 } else if (buf[0] == '2') {
8897 g_warning("Couldn't write to file\n");
8898 } else if (buf[0] == '3') {
8899 g_warning("Pipe read failed\n");
8902 compose_set_ext_editor_sensitive(compose, TRUE);
8904 g_free(compose->exteditor_file);
8905 compose->exteditor_file = NULL;
8906 compose->exteditor_pid = -1;
8907 compose->exteditor_ch = NULL;
8908 compose->exteditor_tag = -1;
8913 static void compose_set_ext_editor_sensitive(Compose *compose,
8916 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8917 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8918 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8919 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8920 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8921 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8922 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8924 gtk_widget_set_sensitive(compose->text, sensitive);
8925 if (compose->toolbar->send_btn)
8926 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8927 if (compose->toolbar->sendl_btn)
8928 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8929 if (compose->toolbar->draft_btn)
8930 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8931 if (compose->toolbar->insert_btn)
8932 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8933 if (compose->toolbar->sig_btn)
8934 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8935 if (compose->toolbar->exteditor_btn)
8936 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8937 if (compose->toolbar->linewrap_current_btn)
8938 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8939 if (compose->toolbar->linewrap_all_btn)
8940 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8942 #endif /* G_OS_UNIX */
8945 * compose_undo_state_changed:
8947 * Change the sensivity of the menuentries undo and redo
8949 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8950 gint redo_state, gpointer data)
8952 Compose *compose = (Compose *)data;
8954 switch (undo_state) {
8955 case UNDO_STATE_TRUE:
8956 if (!undostruct->undo_state) {
8957 undostruct->undo_state = TRUE;
8958 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
8961 case UNDO_STATE_FALSE:
8962 if (undostruct->undo_state) {
8963 undostruct->undo_state = FALSE;
8964 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8967 case UNDO_STATE_UNCHANGED:
8969 case UNDO_STATE_REFRESH:
8970 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
8973 g_warning("Undo state not recognized");
8977 switch (redo_state) {
8978 case UNDO_STATE_TRUE:
8979 if (!undostruct->redo_state) {
8980 undostruct->redo_state = TRUE;
8981 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
8984 case UNDO_STATE_FALSE:
8985 if (undostruct->redo_state) {
8986 undostruct->redo_state = FALSE;
8987 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
8990 case UNDO_STATE_UNCHANGED:
8992 case UNDO_STATE_REFRESH:
8993 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
8996 g_warning("Redo state not recognized");
9001 /* callback functions */
9003 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9004 * includes "non-client" (windows-izm) in calculation, so this calculation
9005 * may not be accurate.
9007 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9008 GtkAllocation *allocation,
9009 GtkSHRuler *shruler)
9011 if (prefs_common.show_ruler) {
9012 gint char_width = 0, char_height = 0;
9013 gint line_width_in_chars;
9015 gtkut_get_font_size(GTK_WIDGET(widget),
9016 &char_width, &char_height);
9017 line_width_in_chars =
9018 (allocation->width - allocation->x) / char_width;
9020 /* got the maximum */
9021 gtk_ruler_set_range(GTK_RULER(shruler),
9022 0.0, line_width_in_chars, 0,
9023 /*line_width_in_chars*/ char_width);
9030 ComposeEntryType header;
9032 ComposePrefType type;
9033 gboolean entry_marked;
9036 static void account_activated(GtkComboBox *optmenu, gpointer data)
9038 Compose *compose = (Compose *)data;
9041 gchar *folderidentifier;
9042 gint account_id = 0;
9045 GSList *list, *saved_list = NULL;
9046 HeaderEntryState *state;
9047 GtkRcStyle *style = NULL;
9048 static GdkColor yellow;
9049 static gboolean color_set = FALSE;
9051 /* Get ID of active account in the combo box */
9052 menu = gtk_combo_box_get_model(optmenu);
9053 gtk_combo_box_get_active_iter(optmenu, &iter);
9054 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9056 ac = account_find_from_id(account_id);
9057 cm_return_if_fail(ac != NULL);
9059 if (ac != compose->account) {
9060 compose_select_account(compose, ac, FALSE);
9062 for (list = compose->header_list; list; list = list->next) {
9063 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9065 if (hentry->type == PREF_ACCOUNT || !list->next) {
9066 compose_destroy_headerentry(compose, hentry);
9070 state = g_malloc0(sizeof(HeaderEntryState));
9071 state->header = combobox_get_active_data(
9072 GTK_COMBO_BOX(hentry->combo));
9073 state->entry = gtk_editable_get_chars(
9074 GTK_EDITABLE(hentry->entry), 0, -1);
9075 state->type = hentry->type;
9078 gdk_color_parse("#f5f6be", &yellow);
9079 color_set = gdk_colormap_alloc_color(
9080 gdk_colormap_get_system(),
9081 &yellow, FALSE, TRUE);
9084 style = gtk_widget_get_modifier_style(hentry->entry);
9085 state->entry_marked = gdk_color_equal(&yellow,
9086 &style->base[GTK_STATE_NORMAL]);
9088 saved_list = g_slist_append(saved_list, state);
9089 compose_destroy_headerentry(compose, hentry);
9092 compose->header_last = NULL;
9093 g_slist_free(compose->header_list);
9094 compose->header_list = NULL;
9095 compose->header_nextrow = 1;
9096 compose_create_header_entry(compose);
9098 if (ac->set_autocc && ac->auto_cc)
9099 compose_entry_append(compose, ac->auto_cc,
9100 COMPOSE_CC, PREF_ACCOUNT);
9102 if (ac->set_autobcc && ac->auto_bcc)
9103 compose_entry_append(compose, ac->auto_bcc,
9104 COMPOSE_BCC, PREF_ACCOUNT);
9106 if (ac->set_autoreplyto && ac->auto_replyto)
9107 compose_entry_append(compose, ac->auto_replyto,
9108 COMPOSE_REPLYTO, PREF_ACCOUNT);
9110 for (list = saved_list; list; list = list->next) {
9111 state = (HeaderEntryState *) list->data;
9113 compose_entry_append(compose, state->entry,
9114 state->header, state->type);
9115 if (state->entry_marked)
9116 compose_entry_mark_default_to(compose, state->entry);
9118 g_free(state->entry);
9120 g_slist_free(saved_list);
9122 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9123 (ac->protocol == A_NNTP) ?
9124 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9127 /* Set message save folder */
9128 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9129 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9131 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9132 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9134 compose_set_save_to(compose, NULL);
9135 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9136 folderidentifier = folder_item_get_identifier(account_get_special_folder
9137 (compose->account, F_OUTBOX));
9138 compose_set_save_to(compose, folderidentifier);
9139 g_free(folderidentifier);
9143 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9144 GtkTreeViewColumn *column, Compose *compose)
9146 compose_attach_property(NULL, compose);
9149 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9152 Compose *compose = (Compose *)data;
9153 GtkTreeSelection *attach_selection;
9154 gint attach_nr_selected;
9156 if (!event) return FALSE;
9158 if (event->button == 3) {
9159 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9160 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9162 if (attach_nr_selected > 0)
9164 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9165 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9167 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9168 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9171 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9172 NULL, NULL, event->button, event->time);
9179 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9182 Compose *compose = (Compose *)data;
9184 if (!event) return FALSE;
9186 switch (event->keyval) {
9188 compose_attach_remove_selected(NULL, compose);
9194 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9196 toolbar_comp_set_sensitive(compose, allow);
9197 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9198 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9200 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9202 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9203 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9204 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9206 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9210 static void compose_send_cb(GtkAction *action, gpointer data)
9212 Compose *compose = (Compose *)data;
9214 if (prefs_common.work_offline &&
9215 !inc_offline_should_override(TRUE,
9216 _("Claws Mail needs network access in order "
9217 "to send this email.")))
9220 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9221 g_source_remove(compose->draft_timeout_tag);
9222 compose->draft_timeout_tag = -1;
9225 compose_send(compose);
9228 static void compose_send_later_cb(GtkAction *action, gpointer data)
9230 Compose *compose = (Compose *)data;
9234 compose_allow_user_actions(compose, FALSE);
9235 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9236 compose_allow_user_actions(compose, TRUE);
9240 compose_close(compose);
9241 } else if (val == -1) {
9242 alertpanel_error(_("Could not queue message."));
9243 } else if (val == -2) {
9244 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9245 } else if (val == -3) {
9246 if (privacy_peek_error())
9247 alertpanel_error(_("Could not queue message for sending:\n\n"
9248 "Signature failed: %s"), privacy_get_error());
9249 } else if (val == -4) {
9250 alertpanel_error(_("Could not queue message for sending:\n\n"
9251 "Charset conversion failed."));
9252 } else if (val == -5) {
9253 alertpanel_error(_("Could not queue message for sending:\n\n"
9254 "Couldn't get recipient encryption key."));
9255 } else if (val == -6) {
9258 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9261 #define DRAFTED_AT_EXIT "drafted_at_exit"
9262 static void compose_register_draft(MsgInfo *info)
9264 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9265 DRAFTED_AT_EXIT, NULL);
9266 FILE *fp = g_fopen(filepath, "ab");
9269 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9277 gboolean compose_draft (gpointer data, guint action)
9279 Compose *compose = (Compose *)data;
9283 MsgFlags flag = {0, 0};
9284 static gboolean lock = FALSE;
9285 MsgInfo *newmsginfo;
9287 gboolean target_locked = FALSE;
9288 gboolean err = FALSE;
9290 if (lock) return FALSE;
9292 if (compose->sending)
9295 draft = account_get_special_folder(compose->account, F_DRAFT);
9296 cm_return_val_if_fail(draft != NULL, FALSE);
9298 if (!g_mutex_trylock(compose->mutex)) {
9299 /* we don't want to lock the mutex once it's available,
9300 * because as the only other part of compose.c locking
9301 * it is compose_close - which means once unlocked,
9302 * the compose struct will be freed */
9303 debug_print("couldn't lock mutex, probably sending\n");
9309 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9310 G_DIR_SEPARATOR, compose);
9311 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9312 FILE_OP_ERROR(tmp, "fopen");
9316 /* chmod for security */
9317 if (change_file_mode_rw(fp, tmp) < 0) {
9318 FILE_OP_ERROR(tmp, "chmod");
9319 g_warning("can't change file mode\n");
9322 /* Save draft infos */
9323 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9324 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9326 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9327 gchar *savefolderid;
9329 savefolderid = compose_get_save_to(compose);
9330 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9331 g_free(savefolderid);
9333 if (compose->return_receipt) {
9334 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9336 if (compose->privacy_system) {
9337 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9338 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9339 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9342 /* Message-ID of message replying to */
9343 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9346 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9347 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9350 /* Message-ID of message forwarding to */
9351 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9354 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9355 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9359 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9360 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9362 /* end of headers */
9363 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9370 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9374 if (fclose(fp) == EOF) {
9378 if (compose->targetinfo) {
9379 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9380 flag.perm_flags = target_locked?MSG_LOCKED:0;
9382 flag.tmp_flags = MSG_DRAFT;
9384 folder_item_scan(draft);
9385 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9386 MsgInfo *tmpinfo = NULL;
9387 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9388 if (compose->msgid) {
9389 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9392 msgnum = tmpinfo->msgnum;
9393 procmsg_msginfo_free(tmpinfo);
9394 debug_print("got draft msgnum %d from scanning\n", msgnum);
9396 debug_print("didn't get draft msgnum after scanning\n");
9399 debug_print("got draft msgnum %d from adding\n", msgnum);
9405 if (action != COMPOSE_AUTO_SAVE) {
9406 if (action != COMPOSE_DRAFT_FOR_EXIT)
9407 alertpanel_error(_("Could not save draft."));
9410 gtkut_window_popup(compose->window);
9411 val = alertpanel_full(_("Could not save draft"),
9412 _("Could not save draft.\n"
9413 "Do you want to cancel exit or discard this email?"),
9414 _("_Cancel exit"), _("_Discard email"), NULL,
9415 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9416 if (val == G_ALERTALTERNATE) {
9418 g_mutex_unlock(compose->mutex); /* must be done before closing */
9419 compose_close(compose);
9423 g_mutex_unlock(compose->mutex); /* must be done before closing */
9432 if (compose->mode == COMPOSE_REEDIT) {
9433 compose_remove_reedit_target(compose, TRUE);
9436 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9439 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9441 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9443 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9444 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9445 procmsg_msginfo_set_flags(newmsginfo, 0,
9446 MSG_HAS_ATTACHMENT);
9448 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9449 compose_register_draft(newmsginfo);
9451 procmsg_msginfo_free(newmsginfo);
9454 folder_item_scan(draft);
9456 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9458 g_mutex_unlock(compose->mutex); /* must be done before closing */
9459 compose_close(compose);
9465 path = folder_item_fetch_msg(draft, msgnum);
9467 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9470 if (g_stat(path, &s) < 0) {
9471 FILE_OP_ERROR(path, "stat");
9477 procmsg_msginfo_free(compose->targetinfo);
9478 compose->targetinfo = procmsg_msginfo_new();
9479 compose->targetinfo->msgnum = msgnum;
9480 compose->targetinfo->size = (goffset)s.st_size;
9481 compose->targetinfo->mtime = s.st_mtime;
9482 compose->targetinfo->folder = draft;
9484 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9485 compose->mode = COMPOSE_REEDIT;
9487 if (action == COMPOSE_AUTO_SAVE) {
9488 compose->autosaved_draft = compose->targetinfo;
9490 compose->modified = FALSE;
9491 compose_set_title(compose);
9495 g_mutex_unlock(compose->mutex);
9499 void compose_clear_exit_drafts(void)
9501 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9502 DRAFTED_AT_EXIT, NULL);
9503 if (is_file_exist(filepath))
9504 claws_unlink(filepath);
9509 void compose_reopen_exit_drafts(void)
9511 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9512 DRAFTED_AT_EXIT, NULL);
9513 FILE *fp = g_fopen(filepath, "rb");
9517 while (fgets(buf, sizeof(buf), fp)) {
9518 gchar **parts = g_strsplit(buf, "\t", 2);
9519 const gchar *folder = parts[0];
9520 int msgnum = parts[1] ? atoi(parts[1]):-1;
9522 if (folder && *folder && msgnum > -1) {
9523 FolderItem *item = folder_find_item_from_identifier(folder);
9524 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9526 compose_reedit(info, FALSE);
9533 compose_clear_exit_drafts();
9536 static void compose_save_cb(GtkAction *action, gpointer data)
9538 Compose *compose = (Compose *)data;
9539 compose_draft(compose, COMPOSE_KEEP_EDITING);
9540 compose->rmode = COMPOSE_REEDIT;
9543 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9545 if (compose && file_list) {
9548 for ( tmp = file_list; tmp; tmp = tmp->next) {
9549 gchar *file = (gchar *) tmp->data;
9550 gchar *utf8_filename = conv_filename_to_utf8(file);
9551 compose_attach_append(compose, file, utf8_filename, NULL);
9552 compose_changed_cb(NULL, compose);
9557 g_free(utf8_filename);
9562 static void compose_attach_cb(GtkAction *action, gpointer data)
9564 Compose *compose = (Compose *)data;
9567 if (compose->redirect_filename != NULL)
9570 file_list = filesel_select_multiple_files_open(_("Select file"));
9573 compose_attach_from_list(compose, file_list, TRUE);
9574 g_list_free(file_list);
9578 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9580 Compose *compose = (Compose *)data;
9582 gint files_inserted = 0;
9584 file_list = filesel_select_multiple_files_open(_("Select file"));
9589 for ( tmp = file_list; tmp; tmp = tmp->next) {
9590 gchar *file = (gchar *) tmp->data;
9591 gchar *filedup = g_strdup(file);
9592 gchar *shortfile = g_path_get_basename(filedup);
9593 ComposeInsertResult res;
9594 /* insert the file if the file is short or if the user confirmed that
9595 he/she wants to insert the large file */
9596 res = compose_insert_file(compose, file);
9597 if (res == COMPOSE_INSERT_READ_ERROR) {
9598 alertpanel_error(_("File '%s' could not be read."), shortfile);
9599 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9600 alertpanel_error(_("File '%s' contained invalid characters\n"
9601 "for the current encoding, insertion may be incorrect."),
9603 } else if (res == COMPOSE_INSERT_SUCCESS)
9610 g_list_free(file_list);
9614 if (files_inserted > 0 && compose->gtkaspell &&
9615 compose->gtkaspell->check_while_typing)
9616 gtkaspell_highlight_all(compose->gtkaspell);
9620 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9622 Compose *compose = (Compose *)data;
9624 compose_insert_sig(compose, FALSE);
9627 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9631 Compose *compose = (Compose *)data;
9633 gtkut_widget_get_uposition(widget, &x, &y);
9634 if (!compose->batch) {
9635 prefs_common.compose_x = x;
9636 prefs_common.compose_y = y;
9638 if (compose->sending || compose->updating)
9640 compose_close_cb(NULL, compose);
9644 void compose_close_toolbar(Compose *compose)
9646 compose_close_cb(NULL, compose);
9649 static void compose_close_cb(GtkAction *action, gpointer data)
9651 Compose *compose = (Compose *)data;
9655 if (compose->exteditor_tag != -1) {
9656 if (!compose_ext_editor_kill(compose))
9661 if (compose->modified) {
9662 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9663 if (!g_mutex_trylock(compose->mutex)) {
9664 /* we don't want to lock the mutex once it's available,
9665 * because as the only other part of compose.c locking
9666 * it is compose_close - which means once unlocked,
9667 * the compose struct will be freed */
9668 debug_print("couldn't lock mutex, probably sending\n");
9672 val = alertpanel(_("Discard message"),
9673 _("This message has been modified. Discard it?"),
9674 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9676 val = alertpanel(_("Save changes"),
9677 _("This message has been modified. Save the latest changes?"),
9678 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9680 g_mutex_unlock(compose->mutex);
9682 case G_ALERTDEFAULT:
9683 if (prefs_common.autosave && !reedit)
9684 compose_remove_draft(compose);
9686 case G_ALERTALTERNATE:
9687 compose_draft(data, COMPOSE_QUIT_EDITING);
9694 compose_close(compose);
9697 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9699 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9700 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9701 Compose *compose = (Compose *) data;
9704 compose->out_encoding = (CharSet)value;
9707 static void compose_address_cb(GtkAction *action, gpointer data)
9709 Compose *compose = (Compose *)data;
9711 addressbook_open(compose);
9714 static void about_show_cb(GtkAction *action, gpointer data)
9719 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9721 Compose *compose = (Compose *)data;
9726 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9727 cm_return_if_fail(tmpl != NULL);
9729 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9731 val = alertpanel(_("Apply template"), msg,
9732 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9735 if (val == G_ALERTDEFAULT)
9736 compose_template_apply(compose, tmpl, TRUE);
9737 else if (val == G_ALERTALTERNATE)
9738 compose_template_apply(compose, tmpl, FALSE);
9741 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9743 Compose *compose = (Compose *)data;
9745 compose_exec_ext_editor(compose);
9748 static void compose_undo_cb(GtkAction *action, gpointer data)
9750 Compose *compose = (Compose *)data;
9751 gboolean prev_autowrap = compose->autowrap;
9753 compose->autowrap = FALSE;
9754 undo_undo(compose->undostruct);
9755 compose->autowrap = prev_autowrap;
9758 static void compose_redo_cb(GtkAction *action, gpointer data)
9760 Compose *compose = (Compose *)data;
9761 gboolean prev_autowrap = compose->autowrap;
9763 compose->autowrap = FALSE;
9764 undo_redo(compose->undostruct);
9765 compose->autowrap = prev_autowrap;
9768 static void entry_cut_clipboard(GtkWidget *entry)
9770 if (GTK_IS_EDITABLE(entry))
9771 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9772 else if (GTK_IS_TEXT_VIEW(entry))
9773 gtk_text_buffer_cut_clipboard(
9774 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9775 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9779 static void entry_copy_clipboard(GtkWidget *entry)
9781 if (GTK_IS_EDITABLE(entry))
9782 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9783 else if (GTK_IS_TEXT_VIEW(entry))
9784 gtk_text_buffer_copy_clipboard(
9785 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9786 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9789 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9790 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9792 if (GTK_IS_TEXT_VIEW(entry)) {
9793 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9794 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9795 GtkTextIter start_iter, end_iter;
9797 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9799 if (contents == NULL)
9802 /* we shouldn't delete the selection when middle-click-pasting, or we
9803 * can't mid-click-paste our own selection */
9804 if (clip != GDK_SELECTION_PRIMARY) {
9805 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9806 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9809 if (insert_place == NULL) {
9810 /* if insert_place isn't specified, insert at the cursor.
9811 * used for Ctrl-V pasting */
9812 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9813 start = gtk_text_iter_get_offset(&start_iter);
9814 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9816 /* if insert_place is specified, paste here.
9817 * used for mid-click-pasting */
9818 start = gtk_text_iter_get_offset(insert_place);
9819 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9820 if (prefs_common.primary_paste_unselects)
9821 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9825 /* paste unwrapped: mark the paste so it's not wrapped later */
9826 end = start + strlen(contents);
9827 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9828 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9829 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9830 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9831 /* rewrap paragraph now (after a mid-click-paste) */
9832 mark_start = gtk_text_buffer_get_insert(buffer);
9833 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9834 gtk_text_iter_backward_char(&start_iter);
9835 compose_beautify_paragraph(compose, &start_iter, TRUE);
9837 } else if (GTK_IS_EDITABLE(entry))
9838 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9840 compose->modified = TRUE;
9843 static void entry_allsel(GtkWidget *entry)
9845 if (GTK_IS_EDITABLE(entry))
9846 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9847 else if (GTK_IS_TEXT_VIEW(entry)) {
9848 GtkTextIter startiter, enditer;
9849 GtkTextBuffer *textbuf;
9851 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9852 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9853 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9855 gtk_text_buffer_move_mark_by_name(textbuf,
9856 "selection_bound", &startiter);
9857 gtk_text_buffer_move_mark_by_name(textbuf,
9858 "insert", &enditer);
9862 static void compose_cut_cb(GtkAction *action, gpointer data)
9864 Compose *compose = (Compose *)data;
9865 if (compose->focused_editable
9866 #ifndef GENERIC_UMPC
9867 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9870 entry_cut_clipboard(compose->focused_editable);
9873 static void compose_copy_cb(GtkAction *action, gpointer data)
9875 Compose *compose = (Compose *)data;
9876 if (compose->focused_editable
9877 #ifndef GENERIC_UMPC
9878 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9881 entry_copy_clipboard(compose->focused_editable);
9884 static void compose_paste_cb(GtkAction *action, gpointer data)
9886 Compose *compose = (Compose *)data;
9888 GtkTextBuffer *buffer;
9890 if (compose->focused_editable &&
9891 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9892 entry_paste_clipboard(compose, compose->focused_editable,
9893 prefs_common.linewrap_pastes,
9894 GDK_SELECTION_CLIPBOARD, NULL);
9898 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9899 compose->gtkaspell &&
9900 compose->gtkaspell->check_while_typing)
9901 gtkaspell_highlight_all(compose->gtkaspell);
9905 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9907 Compose *compose = (Compose *)data;
9908 gint wrap_quote = prefs_common.linewrap_quote;
9909 if (compose->focused_editable
9910 #ifndef GENERIC_UMPC
9911 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9914 /* let text_insert() (called directly or at a later time
9915 * after the gtk_editable_paste_clipboard) know that
9916 * text is to be inserted as a quotation. implemented
9917 * by using a simple refcount... */
9918 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9919 G_OBJECT(compose->focused_editable),
9920 "paste_as_quotation"));
9921 g_object_set_data(G_OBJECT(compose->focused_editable),
9922 "paste_as_quotation",
9923 GINT_TO_POINTER(paste_as_quotation + 1));
9924 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9925 entry_paste_clipboard(compose, compose->focused_editable,
9926 prefs_common.linewrap_pastes,
9927 GDK_SELECTION_CLIPBOARD, NULL);
9928 prefs_common.linewrap_quote = wrap_quote;
9932 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9934 Compose *compose = (Compose *)data;
9936 GtkTextBuffer *buffer;
9938 if (compose->focused_editable
9939 #ifndef GENERIC_UMPC
9940 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9943 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9944 GDK_SELECTION_CLIPBOARD, NULL);
9948 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9949 compose->gtkaspell &&
9950 compose->gtkaspell->check_while_typing)
9951 gtkaspell_highlight_all(compose->gtkaspell);
9955 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
9957 Compose *compose = (Compose *)data;
9959 GtkTextBuffer *buffer;
9961 if (compose->focused_editable
9962 #ifndef GENERIC_UMPC
9963 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9966 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9967 GDK_SELECTION_CLIPBOARD, NULL);
9971 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9972 compose->gtkaspell &&
9973 compose->gtkaspell->check_while_typing)
9974 gtkaspell_highlight_all(compose->gtkaspell);
9978 static void compose_allsel_cb(GtkAction *action, gpointer data)
9980 Compose *compose = (Compose *)data;
9981 if (compose->focused_editable
9982 #ifndef GENERIC_UMPC
9983 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9986 entry_allsel(compose->focused_editable);
9989 static void textview_move_beginning_of_line (GtkTextView *text)
9991 GtkTextBuffer *buffer;
9995 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
9997 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9998 mark = gtk_text_buffer_get_insert(buffer);
9999 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10000 gtk_text_iter_set_line_offset(&ins, 0);
10001 gtk_text_buffer_place_cursor(buffer, &ins);
10004 static void textview_move_forward_character (GtkTextView *text)
10006 GtkTextBuffer *buffer;
10010 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10012 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10013 mark = gtk_text_buffer_get_insert(buffer);
10014 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10015 if (gtk_text_iter_forward_cursor_position(&ins))
10016 gtk_text_buffer_place_cursor(buffer, &ins);
10019 static void textview_move_backward_character (GtkTextView *text)
10021 GtkTextBuffer *buffer;
10025 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10027 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10028 mark = gtk_text_buffer_get_insert(buffer);
10029 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10030 if (gtk_text_iter_backward_cursor_position(&ins))
10031 gtk_text_buffer_place_cursor(buffer, &ins);
10034 static void textview_move_forward_word (GtkTextView *text)
10036 GtkTextBuffer *buffer;
10041 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10043 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10044 mark = gtk_text_buffer_get_insert(buffer);
10045 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10046 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10047 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10048 gtk_text_iter_backward_word_start(&ins);
10049 gtk_text_buffer_place_cursor(buffer, &ins);
10053 static void textview_move_backward_word (GtkTextView *text)
10055 GtkTextBuffer *buffer;
10060 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10062 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10063 mark = gtk_text_buffer_get_insert(buffer);
10064 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10065 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10066 if (gtk_text_iter_backward_word_starts(&ins, 1))
10067 gtk_text_buffer_place_cursor(buffer, &ins);
10070 static void textview_move_end_of_line (GtkTextView *text)
10072 GtkTextBuffer *buffer;
10076 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10078 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10079 mark = gtk_text_buffer_get_insert(buffer);
10080 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10081 if (gtk_text_iter_forward_to_line_end(&ins))
10082 gtk_text_buffer_place_cursor(buffer, &ins);
10085 static void textview_move_next_line (GtkTextView *text)
10087 GtkTextBuffer *buffer;
10092 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10094 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10095 mark = gtk_text_buffer_get_insert(buffer);
10096 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10097 offset = gtk_text_iter_get_line_offset(&ins);
10098 if (gtk_text_iter_forward_line(&ins)) {
10099 gtk_text_iter_set_line_offset(&ins, offset);
10100 gtk_text_buffer_place_cursor(buffer, &ins);
10104 static void textview_move_previous_line (GtkTextView *text)
10106 GtkTextBuffer *buffer;
10111 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10113 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10114 mark = gtk_text_buffer_get_insert(buffer);
10115 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10116 offset = gtk_text_iter_get_line_offset(&ins);
10117 if (gtk_text_iter_backward_line(&ins)) {
10118 gtk_text_iter_set_line_offset(&ins, offset);
10119 gtk_text_buffer_place_cursor(buffer, &ins);
10123 static void textview_delete_forward_character (GtkTextView *text)
10125 GtkTextBuffer *buffer;
10127 GtkTextIter ins, end_iter;
10129 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10132 mark = gtk_text_buffer_get_insert(buffer);
10133 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10135 if (gtk_text_iter_forward_char(&end_iter)) {
10136 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10140 static void textview_delete_backward_character (GtkTextView *text)
10142 GtkTextBuffer *buffer;
10144 GtkTextIter ins, end_iter;
10146 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10148 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10149 mark = gtk_text_buffer_get_insert(buffer);
10150 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10152 if (gtk_text_iter_backward_char(&end_iter)) {
10153 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10157 static void textview_delete_forward_word (GtkTextView *text)
10159 GtkTextBuffer *buffer;
10161 GtkTextIter ins, end_iter;
10163 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10165 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10166 mark = gtk_text_buffer_get_insert(buffer);
10167 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10169 if (gtk_text_iter_forward_word_end(&end_iter)) {
10170 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10174 static void textview_delete_backward_word (GtkTextView *text)
10176 GtkTextBuffer *buffer;
10178 GtkTextIter ins, end_iter;
10180 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10182 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10183 mark = gtk_text_buffer_get_insert(buffer);
10184 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10186 if (gtk_text_iter_backward_word_start(&end_iter)) {
10187 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10191 static void textview_delete_line (GtkTextView *text)
10193 GtkTextBuffer *buffer;
10195 GtkTextIter ins, start_iter, end_iter;
10197 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10199 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10200 mark = gtk_text_buffer_get_insert(buffer);
10201 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10204 gtk_text_iter_set_line_offset(&start_iter, 0);
10207 if (gtk_text_iter_ends_line(&end_iter)){
10208 if (!gtk_text_iter_forward_char(&end_iter))
10209 gtk_text_iter_backward_char(&start_iter);
10212 gtk_text_iter_forward_to_line_end(&end_iter);
10213 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10216 static void textview_delete_to_line_end (GtkTextView *text)
10218 GtkTextBuffer *buffer;
10220 GtkTextIter ins, end_iter;
10222 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10224 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10225 mark = gtk_text_buffer_get_insert(buffer);
10226 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10228 if (gtk_text_iter_ends_line(&end_iter))
10229 gtk_text_iter_forward_char(&end_iter);
10231 gtk_text_iter_forward_to_line_end(&end_iter);
10232 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10235 #define DO_ACTION(name, act) { \
10236 if(!strcmp(name, a_name)) { \
10240 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10242 const gchar *a_name = gtk_action_get_name(action);
10243 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10244 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10245 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10246 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10247 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10248 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10249 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10250 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10251 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10252 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10253 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10254 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10255 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10256 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10260 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10262 Compose *compose = (Compose *)data;
10263 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10264 ComposeCallAdvancedAction action = -1;
10266 action = compose_call_advanced_action_from_path(gaction);
10269 void (*do_action) (GtkTextView *text);
10270 } action_table[] = {
10271 {textview_move_beginning_of_line},
10272 {textview_move_forward_character},
10273 {textview_move_backward_character},
10274 {textview_move_forward_word},
10275 {textview_move_backward_word},
10276 {textview_move_end_of_line},
10277 {textview_move_next_line},
10278 {textview_move_previous_line},
10279 {textview_delete_forward_character},
10280 {textview_delete_backward_character},
10281 {textview_delete_forward_word},
10282 {textview_delete_backward_word},
10283 {textview_delete_line},
10284 {textview_delete_to_line_end}
10287 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10289 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10290 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10291 if (action_table[action].do_action)
10292 action_table[action].do_action(text);
10294 g_warning("Not implemented yet.");
10298 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10302 if (GTK_IS_EDITABLE(widget)) {
10303 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10304 gtk_editable_set_position(GTK_EDITABLE(widget),
10307 if (widget->parent && widget->parent->parent
10308 && widget->parent->parent->parent) {
10309 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10310 gint y = widget->allocation.y;
10311 gint height = widget->allocation.height;
10312 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10313 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10315 if (y < (int)shown->value) {
10316 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10318 if (y + height > (int)shown->value + (int)shown->page_size) {
10319 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10320 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10321 y + height - (int)shown->page_size - 1);
10323 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10324 (int)shown->upper - (int)shown->page_size - 1);
10331 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10332 compose->focused_editable = widget;
10334 #ifdef GENERIC_UMPC
10335 if (GTK_IS_TEXT_VIEW(widget)
10336 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10337 g_object_ref(compose->notebook);
10338 g_object_ref(compose->edit_vbox);
10339 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10340 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10341 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10342 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10343 g_object_unref(compose->notebook);
10344 g_object_unref(compose->edit_vbox);
10345 g_signal_handlers_block_by_func(G_OBJECT(widget),
10346 G_CALLBACK(compose_grab_focus_cb),
10348 gtk_widget_grab_focus(widget);
10349 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10350 G_CALLBACK(compose_grab_focus_cb),
10352 } else if (!GTK_IS_TEXT_VIEW(widget)
10353 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10354 g_object_ref(compose->notebook);
10355 g_object_ref(compose->edit_vbox);
10356 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10357 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10358 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10359 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10360 g_object_unref(compose->notebook);
10361 g_object_unref(compose->edit_vbox);
10362 g_signal_handlers_block_by_func(G_OBJECT(widget),
10363 G_CALLBACK(compose_grab_focus_cb),
10365 gtk_widget_grab_focus(widget);
10366 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10367 G_CALLBACK(compose_grab_focus_cb),
10373 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10375 compose->modified = TRUE;
10376 // compose_beautify_paragraph(compose, NULL, TRUE);
10377 #ifndef GENERIC_UMPC
10378 compose_set_title(compose);
10382 static void compose_wrap_cb(GtkAction *action, gpointer data)
10384 Compose *compose = (Compose *)data;
10385 compose_beautify_paragraph(compose, NULL, TRUE);
10388 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10390 Compose *compose = (Compose *)data;
10391 compose_wrap_all_full(compose, TRUE);
10394 static void compose_find_cb(GtkAction *action, gpointer data)
10396 Compose *compose = (Compose *)data;
10398 message_search_compose(compose);
10401 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10404 Compose *compose = (Compose *)data;
10405 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10406 if (compose->autowrap)
10407 compose_wrap_all_full(compose, TRUE);
10408 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10411 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10414 Compose *compose = (Compose *)data;
10415 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10418 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10420 Compose *compose = (Compose *)data;
10422 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10425 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10427 Compose *compose = (Compose *)data;
10429 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10432 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10434 g_free(compose->privacy_system);
10436 compose->privacy_system = g_strdup(account->default_privacy_system);
10437 compose_update_privacy_system_menu_item(compose, warn);
10440 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10442 Compose *compose = (Compose *)data;
10444 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10445 gtk_widget_show(compose->ruler_hbox);
10446 prefs_common.show_ruler = TRUE;
10448 gtk_widget_hide(compose->ruler_hbox);
10449 gtk_widget_queue_resize(compose->edit_vbox);
10450 prefs_common.show_ruler = FALSE;
10454 static void compose_attach_drag_received_cb (GtkWidget *widget,
10455 GdkDragContext *context,
10458 GtkSelectionData *data,
10461 gpointer user_data)
10463 Compose *compose = (Compose *)user_data;
10466 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10468 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10470 ) && gtk_drag_get_source_widget(context) !=
10471 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10472 list = uri_list_extract_filenames((const gchar *)data->data);
10473 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10474 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10475 compose_attach_append
10476 (compose, (const gchar *)tmp->data,
10477 utf8_filename, NULL);
10478 g_free(utf8_filename);
10480 if (list) compose_changed_cb(NULL, compose);
10481 list_free_strings(list);
10483 } else if (gtk_drag_get_source_widget(context)
10484 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10485 /* comes from our summaryview */
10486 SummaryView * summaryview = NULL;
10487 GSList * list = NULL, *cur = NULL;
10489 if (mainwindow_get_mainwindow())
10490 summaryview = mainwindow_get_mainwindow()->summaryview;
10493 list = summary_get_selected_msg_list(summaryview);
10495 for (cur = list; cur; cur = cur->next) {
10496 MsgInfo *msginfo = (MsgInfo *)cur->data;
10497 gchar *file = NULL;
10499 file = procmsg_get_message_file_full(msginfo,
10502 compose_attach_append(compose, (const gchar *)file,
10503 (const gchar *)file, "message/rfc822");
10507 g_slist_free(list);
10511 static gboolean compose_drag_drop(GtkWidget *widget,
10512 GdkDragContext *drag_context,
10514 guint time, gpointer user_data)
10516 /* not handling this signal makes compose_insert_drag_received_cb
10521 static void compose_insert_drag_received_cb (GtkWidget *widget,
10522 GdkDragContext *drag_context,
10525 GtkSelectionData *data,
10528 gpointer user_data)
10530 Compose *compose = (Compose *)user_data;
10533 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10536 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10538 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10540 AlertValue val = G_ALERTDEFAULT;
10542 list = uri_list_extract_filenames((const gchar *)data->data);
10543 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10544 /* Assume a list of no files, and data has ://, is a remote link */
10545 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10546 gchar *tmpfile = get_tmp_file();
10547 str_write_to_file(tmpdata, tmpfile);
10549 compose_insert_file(compose, tmpfile);
10550 claws_unlink(tmpfile);
10552 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10553 compose_beautify_paragraph(compose, NULL, TRUE);
10556 switch (prefs_common.compose_dnd_mode) {
10557 case COMPOSE_DND_ASK:
10558 val = alertpanel_full(_("Insert or attach?"),
10559 _("Do you want to insert the contents of the file(s) "
10560 "into the message body, or attach it to the email?"),
10561 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10562 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10564 case COMPOSE_DND_INSERT:
10565 val = G_ALERTALTERNATE;
10567 case COMPOSE_DND_ATTACH:
10568 val = G_ALERTOTHER;
10571 /* unexpected case */
10572 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10575 if (val & G_ALERTDISABLE) {
10576 val &= ~G_ALERTDISABLE;
10577 /* remember what action to perform by default, only if we don't click Cancel */
10578 if (val == G_ALERTALTERNATE)
10579 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10580 else if (val == G_ALERTOTHER)
10581 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10584 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10585 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10586 list_free_strings(list);
10589 } else if (val == G_ALERTOTHER) {
10590 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10591 list_free_strings(list);
10596 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10597 compose_insert_file(compose, (const gchar *)tmp->data);
10599 list_free_strings(list);
10601 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10604 #if GTK_CHECK_VERSION(2, 8, 0)
10605 /* do nothing, handled by GTK */
10607 gchar *tmpfile = get_tmp_file();
10608 str_write_to_file((const gchar *)data->data, tmpfile);
10609 compose_insert_file(compose, tmpfile);
10610 claws_unlink(tmpfile);
10612 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10616 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10619 static void compose_header_drag_received_cb (GtkWidget *widget,
10620 GdkDragContext *drag_context,
10623 GtkSelectionData *data,
10626 gpointer user_data)
10628 GtkEditable *entry = (GtkEditable *)user_data;
10629 gchar *email = (gchar *)data->data;
10631 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10634 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10635 gchar *decoded=g_new(gchar, strlen(email));
10638 email += strlen("mailto:");
10639 decode_uri(decoded, email); /* will fit */
10640 gtk_editable_delete_text(entry, 0, -1);
10641 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10642 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10646 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10649 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10651 Compose *compose = (Compose *)data;
10653 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10654 compose->return_receipt = TRUE;
10656 compose->return_receipt = FALSE;
10659 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10661 Compose *compose = (Compose *)data;
10663 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10664 compose->remove_references = TRUE;
10666 compose->remove_references = FALSE;
10669 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10670 ComposeHeaderEntry *headerentry)
10672 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10676 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10677 GdkEventKey *event,
10678 ComposeHeaderEntry *headerentry)
10680 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10681 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10682 !(event->state & GDK_MODIFIER_MASK) &&
10683 (event->keyval == GDK_BackSpace) &&
10684 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10685 gtk_container_remove
10686 (GTK_CONTAINER(headerentry->compose->header_table),
10687 headerentry->combo);
10688 gtk_container_remove
10689 (GTK_CONTAINER(headerentry->compose->header_table),
10690 headerentry->entry);
10691 headerentry->compose->header_list =
10692 g_slist_remove(headerentry->compose->header_list,
10694 g_free(headerentry);
10695 } else if (event->keyval == GDK_Tab) {
10696 if (headerentry->compose->header_last == headerentry) {
10697 /* Override default next focus, and give it to subject_entry
10698 * instead of notebook tabs
10700 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10701 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10708 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10709 ComposeHeaderEntry *headerentry)
10711 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10712 compose_create_header_entry(headerentry->compose);
10713 g_signal_handlers_disconnect_matched
10714 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10715 0, 0, NULL, NULL, headerentry);
10717 /* Automatically scroll down */
10718 GTK_EVENTS_FLUSH();
10719 compose_show_first_last_header(headerentry->compose, FALSE);
10725 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10727 GtkAdjustment *vadj;
10729 cm_return_if_fail(compose);
10730 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10731 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10732 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10733 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10734 gtk_adjustment_changed(vadj);
10737 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10738 const gchar *text, gint len, Compose *compose)
10740 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10741 (G_OBJECT(compose->text), "paste_as_quotation"));
10744 cm_return_if_fail(text != NULL);
10746 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10747 G_CALLBACK(text_inserted),
10749 if (paste_as_quotation) {
10751 const gchar *qmark;
10753 GtkTextIter start_iter;
10756 len = strlen(text);
10758 new_text = g_strndup(text, len);
10760 qmark = compose_quote_char_from_context(compose);
10762 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10763 gtk_text_buffer_place_cursor(buffer, iter);
10765 pos = gtk_text_iter_get_offset(iter);
10767 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10768 _("Quote format error at line %d."));
10769 quote_fmt_reset_vartable();
10771 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10772 GINT_TO_POINTER(paste_as_quotation - 1));
10774 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10775 gtk_text_buffer_place_cursor(buffer, iter);
10776 gtk_text_buffer_delete_mark(buffer, mark);
10778 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10779 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10780 compose_beautify_paragraph(compose, &start_iter, FALSE);
10781 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10782 gtk_text_buffer_delete_mark(buffer, mark);
10784 if (strcmp(text, "\n") || compose->automatic_break
10785 || gtk_text_iter_starts_line(iter)) {
10786 GtkTextIter before_ins;
10787 gtk_text_buffer_insert(buffer, iter, text, len);
10788 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10789 before_ins = *iter;
10790 gtk_text_iter_backward_chars(&before_ins, len);
10791 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10794 /* check if the preceding is just whitespace or quote */
10795 GtkTextIter start_line;
10796 gchar *tmp = NULL, *quote = NULL;
10797 gint quote_len = 0, is_normal = 0;
10798 start_line = *iter;
10799 gtk_text_iter_set_line_offset(&start_line, 0);
10800 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10803 if (*tmp == '\0') {
10806 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10814 gtk_text_buffer_insert(buffer, iter, text, len);
10816 gtk_text_buffer_insert_with_tags_by_name(buffer,
10817 iter, text, len, "no_join", NULL);
10822 if (!paste_as_quotation) {
10823 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10824 compose_beautify_paragraph(compose, iter, FALSE);
10825 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10826 gtk_text_buffer_delete_mark(buffer, mark);
10829 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10830 G_CALLBACK(text_inserted),
10832 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10834 if (prefs_common.autosave &&
10835 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10836 compose->draft_timeout_tag != -2 /* disabled while loading */)
10837 compose->draft_timeout_tag = g_timeout_add
10838 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10840 static gint compose_defer_auto_save_draft(Compose *compose)
10842 compose->draft_timeout_tag = -1;
10843 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10848 static void compose_check_all(GtkAction *action, gpointer data)
10850 Compose *compose = (Compose *)data;
10851 if (!compose->gtkaspell)
10854 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10855 claws_spell_entry_check_all(
10856 CLAWS_SPELL_ENTRY(compose->subject_entry));
10858 gtkaspell_check_all(compose->gtkaspell);
10861 static void compose_highlight_all(GtkAction *action, gpointer data)
10863 Compose *compose = (Compose *)data;
10864 if (compose->gtkaspell) {
10865 claws_spell_entry_recheck_all(
10866 CLAWS_SPELL_ENTRY(compose->subject_entry));
10867 gtkaspell_highlight_all(compose->gtkaspell);
10871 static void compose_check_backwards(GtkAction *action, gpointer data)
10873 Compose *compose = (Compose *)data;
10874 if (!compose->gtkaspell) {
10875 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10879 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10880 claws_spell_entry_check_backwards(
10881 CLAWS_SPELL_ENTRY(compose->subject_entry));
10883 gtkaspell_check_backwards(compose->gtkaspell);
10886 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10888 Compose *compose = (Compose *)data;
10889 if (!compose->gtkaspell) {
10890 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10894 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10895 claws_spell_entry_check_forwards_go(
10896 CLAWS_SPELL_ENTRY(compose->subject_entry));
10898 gtkaspell_check_forwards_go(compose->gtkaspell);
10903 *\brief Guess originating forward account from MsgInfo and several
10904 * "common preference" settings. Return NULL if no guess.
10906 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10908 PrefsAccount *account = NULL;
10910 cm_return_val_if_fail(msginfo, NULL);
10911 cm_return_val_if_fail(msginfo->folder, NULL);
10912 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10914 if (msginfo->folder->prefs->enable_default_account)
10915 account = account_find_from_id(msginfo->folder->prefs->default_account);
10918 account = msginfo->folder->folder->account;
10920 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10922 Xstrdup_a(to, msginfo->to, return NULL);
10923 extract_address(to);
10924 account = account_find_from_address(to, FALSE);
10927 if (!account && prefs_common.forward_account_autosel) {
10928 gchar cc[BUFFSIZE];
10929 if (!procheader_get_header_from_msginfo
10930 (msginfo, cc,sizeof cc , "Cc:")) {
10931 gchar *buf = cc + strlen("Cc:");
10932 extract_address(buf);
10933 account = account_find_from_address(buf, FALSE);
10937 if (!account && prefs_common.forward_account_autosel) {
10938 gchar deliveredto[BUFFSIZE];
10939 if (!procheader_get_header_from_msginfo
10940 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10941 gchar *buf = deliveredto + strlen("Delivered-To:");
10942 extract_address(buf);
10943 account = account_find_from_address(buf, FALSE);
10950 gboolean compose_close(Compose *compose)
10954 if (!g_mutex_trylock(compose->mutex)) {
10955 /* we have to wait for the (possibly deferred by auto-save)
10956 * drafting to be done, before destroying the compose under
10958 debug_print("waiting for drafting to finish...\n");
10959 compose_allow_user_actions(compose, FALSE);
10960 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10963 cm_return_val_if_fail(compose, FALSE);
10964 gtkut_widget_get_uposition(compose->window, &x, &y);
10965 if (!compose->batch) {
10966 prefs_common.compose_x = x;
10967 prefs_common.compose_y = y;
10969 g_mutex_unlock(compose->mutex);
10970 compose_destroy(compose);
10975 * Add entry field for each address in list.
10976 * \param compose E-Mail composition object.
10977 * \param listAddress List of (formatted) E-Mail addresses.
10979 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10982 node = listAddress;
10984 addr = ( gchar * ) node->data;
10985 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
10986 node = g_list_next( node );
10990 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10991 guint action, gboolean opening_multiple)
10993 gchar *body = NULL;
10994 GSList *new_msglist = NULL;
10995 MsgInfo *tmp_msginfo = NULL;
10996 gboolean originally_enc = FALSE;
10997 gboolean originally_sig = FALSE;
10998 Compose *compose = NULL;
10999 gchar *s_system = NULL;
11001 cm_return_if_fail(msgview != NULL);
11003 cm_return_if_fail(msginfo_list != NULL);
11005 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11006 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11007 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11009 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11010 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11011 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11012 orig_msginfo, mimeinfo);
11013 if (tmp_msginfo != NULL) {
11014 new_msglist = g_slist_append(NULL, tmp_msginfo);
11016 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11017 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11018 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11020 tmp_msginfo->folder = orig_msginfo->folder;
11021 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11022 if (orig_msginfo->tags) {
11023 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11024 tmp_msginfo->folder->tags_dirty = TRUE;
11030 if (!opening_multiple)
11031 body = messageview_get_selection(msgview);
11034 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11035 procmsg_msginfo_free(tmp_msginfo);
11036 g_slist_free(new_msglist);
11038 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11040 if (compose && originally_enc) {
11041 compose_force_encryption(compose, compose->account, FALSE, s_system);
11044 if (compose && originally_sig && compose->account->default_sign_reply) {
11045 compose_force_signing(compose, compose->account, s_system);
11051 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11054 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11055 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11056 GSList *cur = msginfo_list;
11057 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11058 "messages. Opening the windows "
11059 "could take some time. Do you "
11060 "want to continue?"),
11061 g_slist_length(msginfo_list));
11062 if (g_slist_length(msginfo_list) > 9
11063 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11064 != G_ALERTALTERNATE) {
11069 /* We'll open multiple compose windows */
11070 /* let the WM place the next windows */
11071 compose_force_window_origin = FALSE;
11072 for (; cur; cur = cur->next) {
11074 tmplist.data = cur->data;
11075 tmplist.next = NULL;
11076 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11078 compose_force_window_origin = TRUE;
11080 /* forwarding multiple mails as attachments is done via a
11081 * single compose window */
11082 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11086 void compose_set_position(Compose *compose, gint pos)
11088 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11090 gtkut_text_view_set_position(text, pos);
11093 gboolean compose_search_string(Compose *compose,
11094 const gchar *str, gboolean case_sens)
11096 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11098 return gtkut_text_view_search_string(text, str, case_sens);
11101 gboolean compose_search_string_backward(Compose *compose,
11102 const gchar *str, gboolean case_sens)
11104 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11106 return gtkut_text_view_search_string_backward(text, str, case_sens);
11109 /* allocate a msginfo structure and populate its data from a compose data structure */
11110 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11112 MsgInfo *newmsginfo;
11114 gchar buf[BUFFSIZE];
11116 cm_return_val_if_fail( compose != NULL, NULL );
11118 newmsginfo = procmsg_msginfo_new();
11121 get_rfc822_date(buf, sizeof(buf));
11122 newmsginfo->date = g_strdup(buf);
11125 if (compose->from_name) {
11126 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11127 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11131 if (compose->subject_entry)
11132 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11134 /* to, cc, reply-to, newsgroups */
11135 for (list = compose->header_list; list; list = list->next) {
11136 gchar *header = gtk_editable_get_chars(
11138 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11139 gchar *entry = gtk_editable_get_chars(
11140 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11142 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11143 if ( newmsginfo->to == NULL ) {
11144 newmsginfo->to = g_strdup(entry);
11145 } else if (entry && *entry) {
11146 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11147 g_free(newmsginfo->to);
11148 newmsginfo->to = tmp;
11151 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11152 if ( newmsginfo->cc == NULL ) {
11153 newmsginfo->cc = g_strdup(entry);
11154 } else if (entry && *entry) {
11155 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11156 g_free(newmsginfo->cc);
11157 newmsginfo->cc = tmp;
11160 if ( strcasecmp(header,
11161 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11162 if ( newmsginfo->newsgroups == NULL ) {
11163 newmsginfo->newsgroups = g_strdup(entry);
11164 } else if (entry && *entry) {
11165 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11166 g_free(newmsginfo->newsgroups);
11167 newmsginfo->newsgroups = tmp;
11175 /* other data is unset */
11181 /* update compose's dictionaries from folder dict settings */
11182 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11183 FolderItem *folder_item)
11185 cm_return_if_fail(compose != NULL);
11187 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11188 FolderItemPrefs *prefs = folder_item->prefs;
11190 if (prefs->enable_default_dictionary)
11191 gtkaspell_change_dict(compose->gtkaspell,
11192 prefs->default_dictionary, FALSE);
11193 if (folder_item->prefs->enable_default_alt_dictionary)
11194 gtkaspell_change_alt_dict(compose->gtkaspell,
11195 prefs->default_alt_dictionary);
11196 if (prefs->enable_default_dictionary
11197 || prefs->enable_default_alt_dictionary)
11198 compose_spell_menu_changed(compose);