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;
3052 gchar *ac_email = NULL;
3054 gboolean reply_to_ml = FALSE;
3055 gboolean default_reply_to = FALSE;
3057 cm_return_if_fail(compose->account != NULL);
3058 cm_return_if_fail(msginfo != NULL);
3060 reply_to_ml = to_ml && compose->ml_post;
3062 default_reply_to = msginfo->folder &&
3063 msginfo->folder->prefs->enable_default_reply_to;
3065 if (compose->account->protocol != A_NNTP) {
3066 if (msginfo && msginfo->folder && msginfo->folder->prefs) {
3067 if (msginfo->folder->prefs->enable_default_replyto) {
3068 compose_entry_append(compose, msginfo->folder->prefs->default_replyto,
3069 COMPOSE_REPLYTO, PREF_FOLDER);
3071 if (msginfo->folder->prefs->enable_default_bcc) {
3072 compose_entry_append(compose, msginfo->folder->prefs->default_bcc,
3073 COMPOSE_BCC, PREF_FOLDER);
3075 if (msginfo->folder->prefs->enable_default_cc) {
3076 compose_entry_append(compose, msginfo->folder->prefs->default_cc,
3077 COMPOSE_CC, PREF_FOLDER);
3080 if (reply_to_ml && !default_reply_to) {
3082 gboolean is_subscr = is_subscription(compose->ml_post,
3085 /* normal answer to ml post with a reply-to */
3086 compose_entry_append(compose,
3088 COMPOSE_TO, PREF_ML);
3089 if (compose->replyto)
3090 compose_entry_append(compose,
3092 COMPOSE_CC, PREF_ML);
3094 /* answer to subscription confirmation */
3095 if (compose->replyto)
3096 compose_entry_append(compose,
3098 COMPOSE_TO, PREF_ML);
3099 else if (msginfo->from)
3100 compose_entry_append(compose,
3102 COMPOSE_TO, PREF_ML);
3105 else if (!(to_all || to_sender) && default_reply_to) {
3106 compose_entry_append(compose,
3107 msginfo->folder->prefs->default_reply_to,
3108 COMPOSE_TO, PREF_FOLDER);
3109 compose_entry_mark_default_to(compose,
3110 msginfo->folder->prefs->default_reply_to);
3115 Xstrdup_a(tmp1, msginfo->from, return);
3116 extract_address(tmp1);
3117 if (to_all || to_sender ||
3118 !account_find_from_address(tmp1, FALSE))
3119 compose_entry_append(compose,
3120 (compose->replyto && !to_sender)
3121 ? compose->replyto :
3122 msginfo->from ? msginfo->from : "",
3123 COMPOSE_TO, PREF_NONE);
3124 else if (!to_all && !to_sender) {
3125 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3126 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3127 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3128 if (compose->replyto) {
3129 compose_entry_append(compose,
3131 COMPOSE_TO, PREF_NONE);
3133 compose_entry_append(compose,
3134 msginfo->from ? msginfo->from : "",
3135 COMPOSE_TO, PREF_NONE);
3138 /* replying to own mail, use original recp */
3139 compose_entry_append(compose,
3140 msginfo->to ? msginfo->to : "",
3141 COMPOSE_TO, PREF_NONE);
3142 compose_entry_append(compose,
3143 msginfo->cc ? msginfo->cc : "",
3144 COMPOSE_CC, PREF_NONE);
3149 if (to_sender || (compose->followup_to &&
3150 !strncmp(compose->followup_to, "poster", 6)))
3151 compose_entry_append
3153 (compose->replyto ? compose->replyto :
3154 msginfo->from ? msginfo->from : ""),
3155 COMPOSE_TO, PREF_NONE);
3157 else if (followup_and_reply_to || to_all) {
3158 compose_entry_append
3160 (compose->replyto ? compose->replyto :
3161 msginfo->from ? msginfo->from : ""),
3162 COMPOSE_TO, PREF_NONE);
3164 compose_entry_append
3166 compose->followup_to ? compose->followup_to :
3167 compose->newsgroups ? compose->newsgroups : "",
3168 COMPOSE_NEWSGROUPS, PREF_NONE);
3171 compose_entry_append
3173 compose->followup_to ? compose->followup_to :
3174 compose->newsgroups ? compose->newsgroups : "",
3175 COMPOSE_NEWSGROUPS, PREF_NONE);
3178 if (msginfo->subject && *msginfo->subject) {
3182 buf = p = g_strdup(msginfo->subject);
3183 p += subject_get_prefix_length(p);
3184 memmove(buf, p, strlen(p) + 1);
3186 buf2 = g_strdup_printf("Re: %s", buf);
3187 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3192 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3194 if (to_ml && compose->ml_post) return;
3195 if (!to_all || compose->account->protocol == A_NNTP) return;
3197 if (compose->replyto) {
3198 Xstrdup_a(replyto, compose->replyto, return);
3199 extract_address(replyto);
3201 if (msginfo->from) {
3202 Xstrdup_a(from, msginfo->from, return);
3203 extract_address(from);
3206 if (replyto && from)
3207 cc_list = address_list_append_with_comments(cc_list, from);
3208 if (to_all && msginfo->folder &&
3209 msginfo->folder->prefs->enable_default_reply_to)
3210 cc_list = address_list_append_with_comments(cc_list,
3211 msginfo->folder->prefs->default_reply_to);
3212 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3213 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3215 ac_email = g_utf8_strdown(compose->account->address, -1);
3218 for (cur = cc_list; cur != NULL; cur = cur->next) {
3219 gchar *addr = g_utf8_strdown(cur->data, -1);
3220 extract_address(addr);
3222 if (strcmp(ac_email, addr))
3223 compose_entry_append(compose, (gchar *)cur->data,
3224 COMPOSE_CC, PREF_NONE);
3226 debug_print("Cc address same as compose account's, ignoring\n");
3231 slist_free_strings(cc_list);
3232 g_slist_free(cc_list);
3238 #define SET_ENTRY(entry, str) \
3241 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3244 #define SET_ADDRESS(type, str) \
3247 compose_entry_append(compose, str, type, PREF_NONE); \
3250 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3252 cm_return_if_fail(msginfo != NULL);
3254 SET_ENTRY(subject_entry, msginfo->subject);
3255 SET_ENTRY(from_name, msginfo->from);
3256 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3257 SET_ADDRESS(COMPOSE_CC, compose->cc);
3258 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3259 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3260 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3261 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3263 compose_update_priority_menu_item(compose);
3264 compose_update_privacy_system_menu_item(compose, FALSE);
3265 compose_show_first_last_header(compose, TRUE);
3271 static void compose_insert_sig(Compose *compose, gboolean replace)
3273 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3274 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3276 GtkTextIter iter, iter_end;
3277 gint cur_pos, ins_pos;
3278 gboolean prev_autowrap;
3279 gboolean found = FALSE;
3280 gboolean exists = FALSE;
3282 cm_return_if_fail(compose->account != NULL);
3286 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3287 G_CALLBACK(compose_changed_cb),
3290 mark = gtk_text_buffer_get_insert(buffer);
3291 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3292 cur_pos = gtk_text_iter_get_offset (&iter);
3295 gtk_text_buffer_get_end_iter(buffer, &iter);
3297 exists = (compose->sig_str != NULL);
3300 GtkTextIter first_iter, start_iter, end_iter;
3302 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3304 if (!exists || compose->sig_str[0] == '\0')
3307 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3308 compose->signature_tag);
3311 /* include previous \n\n */
3312 gtk_text_iter_backward_chars(&first_iter, 1);
3313 start_iter = first_iter;
3314 end_iter = first_iter;
3316 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3317 compose->signature_tag);
3318 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3319 compose->signature_tag);
3321 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3327 g_free(compose->sig_str);
3328 compose->sig_str = account_get_signature_str(compose->account);
3330 cur_pos = gtk_text_iter_get_offset(&iter);
3332 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3333 g_free(compose->sig_str);
3334 compose->sig_str = NULL;
3336 if (compose->sig_inserted == FALSE)
3337 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3338 compose->sig_inserted = TRUE;
3340 cur_pos = gtk_text_iter_get_offset(&iter);
3341 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3343 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3344 gtk_text_iter_forward_chars(&iter, 1);
3345 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3346 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3348 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3349 cur_pos = gtk_text_buffer_get_char_count (buffer);
3352 /* put the cursor where it should be
3353 * either where the quote_fmt says, either where it was */
3354 if (compose->set_cursor_pos < 0)
3355 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3357 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3358 compose->set_cursor_pos);
3360 compose->set_cursor_pos = -1;
3361 gtk_text_buffer_place_cursor(buffer, &iter);
3362 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3363 G_CALLBACK(compose_changed_cb),
3369 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3372 GtkTextBuffer *buffer;
3375 const gchar *cur_encoding;
3376 gchar buf[BUFFSIZE];
3379 gboolean prev_autowrap;
3380 gboolean badtxt = FALSE;
3381 struct stat file_stat;
3384 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3386 /* get the size of the file we are about to insert */
3387 ret = g_stat(file, &file_stat);
3389 gchar *shortfile = g_path_get_basename(file);
3390 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3392 return COMPOSE_INSERT_NO_FILE;
3393 } else if (prefs_common.warn_large_insert == TRUE) {
3395 /* ask user for confirmation if the file is large */
3396 if (prefs_common.warn_large_insert_size < 0 ||
3397 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3401 msg = g_strdup_printf(_("You are about to insert a file of %s "
3402 "in the message body. Are you sure you want to do that?"),
3403 to_human_readable(file_stat.st_size));
3404 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3405 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3408 /* do we ask for confirmation next time? */
3409 if (aval & G_ALERTDISABLE) {
3410 /* no confirmation next time, disable feature in preferences */
3411 aval &= ~G_ALERTDISABLE;
3412 prefs_common.warn_large_insert = FALSE;
3415 /* abort file insertion if user canceled action */
3416 if (aval != G_ALERTALTERNATE) {
3417 return COMPOSE_INSERT_NO_FILE;
3423 if ((fp = g_fopen(file, "rb")) == NULL) {
3424 FILE_OP_ERROR(file, "fopen");
3425 return COMPOSE_INSERT_READ_ERROR;
3428 prev_autowrap = compose->autowrap;
3429 compose->autowrap = FALSE;
3431 text = GTK_TEXT_VIEW(compose->text);
3432 buffer = gtk_text_view_get_buffer(text);
3433 mark = gtk_text_buffer_get_insert(buffer);
3434 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3436 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3437 G_CALLBACK(text_inserted),
3440 cur_encoding = conv_get_locale_charset_str_no_utf8();
3442 while (fgets(buf, sizeof(buf), fp) != NULL) {
3445 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3446 str = g_strdup(buf);
3448 str = conv_codeset_strdup
3449 (buf, cur_encoding, CS_INTERNAL);
3452 /* strip <CR> if DOS/Windows file,
3453 replace <CR> with <LF> if Macintosh file. */
3456 if (len > 0 && str[len - 1] != '\n') {
3458 if (str[len] == '\r') str[len] = '\n';
3461 gtk_text_buffer_insert(buffer, &iter, str, -1);
3465 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3466 G_CALLBACK(text_inserted),
3468 compose->autowrap = prev_autowrap;
3469 if (compose->autowrap)
3470 compose_wrap_all(compose);
3475 return COMPOSE_INSERT_INVALID_CHARACTER;
3477 return COMPOSE_INSERT_SUCCESS;
3480 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3481 const gchar *filename,
3482 const gchar *content_type)
3490 GtkListStore *store;
3492 gboolean has_binary = FALSE;
3494 if (!is_file_exist(file)) {
3495 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3496 gboolean result = FALSE;
3497 if (file_from_uri && is_file_exist(file_from_uri)) {
3498 result = compose_attach_append(
3499 compose, file_from_uri,
3503 g_free(file_from_uri);
3506 alertpanel_error("File %s doesn't exist\n", filename);
3509 if ((size = get_file_size(file)) < 0) {
3510 alertpanel_error("Can't get file size of %s\n", filename);
3514 alertpanel_error(_("File %s is empty."), filename);
3517 if ((fp = g_fopen(file, "rb")) == NULL) {
3518 alertpanel_error(_("Can't read %s."), filename);
3523 ainfo = g_new0(AttachInfo, 1);
3524 auto_ainfo = g_auto_pointer_new_with_free
3525 (ainfo, (GFreeFunc) compose_attach_info_free);
3526 ainfo->file = g_strdup(file);
3529 ainfo->content_type = g_strdup(content_type);
3530 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3532 MsgFlags flags = {0, 0};
3534 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3535 ainfo->encoding = ENC_7BIT;
3537 ainfo->encoding = ENC_8BIT;
3539 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3540 if (msginfo && msginfo->subject)
3541 name = g_strdup(msginfo->subject);
3543 name = g_path_get_basename(filename ? filename : file);
3545 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3547 procmsg_msginfo_free(msginfo);
3549 if (!g_ascii_strncasecmp(content_type, "text", 4))
3550 ainfo->encoding = 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 ainfo->content_type = procmime_get_mime_type(file);
3559 if (!ainfo->content_type) {
3560 ainfo->content_type =
3561 g_strdup("application/octet-stream");
3562 ainfo->encoding = ENC_BASE64;
3563 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3565 procmime_get_encoding_for_text_file(file, &has_binary);
3567 ainfo->encoding = ENC_BASE64;
3568 name = g_path_get_basename(filename ? filename : file);
3569 ainfo->name = g_strdup(name);
3573 if (ainfo->name != NULL
3574 && !strcmp(ainfo->name, ".")) {
3575 g_free(ainfo->name);
3579 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3580 g_free(ainfo->content_type);
3581 ainfo->content_type = g_strdup("application/octet-stream");
3584 ainfo->size = (goffset)size;
3585 size_text = to_human_readable((goffset)size);
3587 store = GTK_LIST_STORE(gtk_tree_view_get_model
3588 (GTK_TREE_VIEW(compose->attach_clist)));
3590 gtk_list_store_append(store, &iter);
3591 gtk_list_store_set(store, &iter,
3592 COL_MIMETYPE, ainfo->content_type,
3593 COL_SIZE, size_text,
3594 COL_NAME, ainfo->name,
3596 COL_AUTODATA, auto_ainfo,
3599 g_auto_pointer_free(auto_ainfo);
3600 compose_attach_update_label(compose);
3604 static void compose_use_signing(Compose *compose, gboolean use_signing)
3606 compose->use_signing = use_signing;
3607 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3610 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3612 compose->use_encryption = use_encryption;
3613 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3616 #define NEXT_PART_NOT_CHILD(info) \
3618 node = info->node; \
3619 while (node->children) \
3620 node = g_node_last_child(node); \
3621 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3624 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3628 MimeInfo *firsttext = NULL;
3629 MimeInfo *encrypted = NULL;
3632 const gchar *partname = NULL;
3634 mimeinfo = procmime_scan_message(msginfo);
3635 if (!mimeinfo) return;
3637 if (mimeinfo->node->children == NULL) {
3638 procmime_mimeinfo_free_all(mimeinfo);
3642 /* find first content part */
3643 child = (MimeInfo *) mimeinfo->node->children->data;
3644 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3645 child = (MimeInfo *)child->node->children->data;
3648 if (child->type == MIMETYPE_TEXT) {
3650 debug_print("First text part found\n");
3651 } else if (compose->mode == COMPOSE_REEDIT &&
3652 child->type == MIMETYPE_APPLICATION &&
3653 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3654 encrypted = (MimeInfo *)child->node->parent->data;
3657 child = (MimeInfo *) mimeinfo->node->children->data;
3658 while (child != NULL) {
3661 if (child == encrypted) {
3662 /* skip this part of tree */
3663 NEXT_PART_NOT_CHILD(child);
3667 if (child->type == MIMETYPE_MULTIPART) {
3668 /* get the actual content */
3669 child = procmime_mimeinfo_next(child);
3673 if (child == firsttext) {
3674 child = procmime_mimeinfo_next(child);
3678 outfile = procmime_get_tmp_file_name(child);
3679 if ((err = procmime_get_part(outfile, child)) < 0)
3680 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3682 gchar *content_type;
3684 content_type = procmime_get_content_type_str(child->type, child->subtype);
3686 /* if we meet a pgp signature, we don't attach it, but
3687 * we force signing. */
3688 if ((strcmp(content_type, "application/pgp-signature") &&
3689 strcmp(content_type, "application/pkcs7-signature") &&
3690 strcmp(content_type, "application/x-pkcs7-signature"))
3691 || compose->mode == COMPOSE_REDIRECT) {
3692 partname = procmime_mimeinfo_get_parameter(child, "filename");
3693 if (partname == NULL)
3694 partname = procmime_mimeinfo_get_parameter(child, "name");
3695 if (partname == NULL)
3697 compose_attach_append(compose, outfile,
3698 partname, content_type);
3700 compose_force_signing(compose, compose->account, NULL);
3702 g_free(content_type);
3705 NEXT_PART_NOT_CHILD(child);
3707 procmime_mimeinfo_free_all(mimeinfo);
3710 #undef NEXT_PART_NOT_CHILD
3715 WAIT_FOR_INDENT_CHAR,
3716 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3719 /* return indent length, we allow:
3720 indent characters followed by indent characters or spaces/tabs,
3721 alphabets and numbers immediately followed by indent characters,
3722 and the repeating sequences of the above
3723 If quote ends with multiple spaces, only the first one is included. */
3724 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3725 const GtkTextIter *start, gint *len)
3727 GtkTextIter iter = *start;
3731 IndentState state = WAIT_FOR_INDENT_CHAR;
3734 gint alnum_count = 0;
3735 gint space_count = 0;
3738 if (prefs_common.quote_chars == NULL) {
3742 while (!gtk_text_iter_ends_line(&iter)) {
3743 wc = gtk_text_iter_get_char(&iter);
3744 if (g_unichar_iswide(wc))
3746 clen = g_unichar_to_utf8(wc, ch);
3750 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3751 is_space = g_unichar_isspace(wc);
3753 if (state == WAIT_FOR_INDENT_CHAR) {
3754 if (!is_indent && !g_unichar_isalnum(wc))
3757 quote_len += alnum_count + space_count + 1;
3758 alnum_count = space_count = 0;
3759 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3762 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3763 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3767 else if (is_indent) {
3768 quote_len += alnum_count + space_count + 1;
3769 alnum_count = space_count = 0;
3772 state = WAIT_FOR_INDENT_CHAR;
3776 gtk_text_iter_forward_char(&iter);
3779 if (quote_len > 0 && space_count > 0)
3785 if (quote_len > 0) {
3787 gtk_text_iter_forward_chars(&iter, quote_len);
3788 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3794 /* return >0 if the line is itemized */
3795 static int compose_itemized_length(GtkTextBuffer *buffer,
3796 const GtkTextIter *start)
3798 GtkTextIter iter = *start;
3803 if (gtk_text_iter_ends_line(&iter))
3808 wc = gtk_text_iter_get_char(&iter);
3809 if (!g_unichar_isspace(wc))
3811 gtk_text_iter_forward_char(&iter);
3812 if (gtk_text_iter_ends_line(&iter))
3816 clen = g_unichar_to_utf8(wc, ch);
3820 if (!strchr("*-+", ch[0]))
3823 gtk_text_iter_forward_char(&iter);
3824 if (gtk_text_iter_ends_line(&iter))
3826 wc = gtk_text_iter_get_char(&iter);
3827 if (g_unichar_isspace(wc)) {
3833 /* return the string at the start of the itemization */
3834 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3835 const GtkTextIter *start)
3837 GtkTextIter iter = *start;
3840 GString *item_chars = g_string_new("");
3843 if (gtk_text_iter_ends_line(&iter))
3848 wc = gtk_text_iter_get_char(&iter);
3849 if (!g_unichar_isspace(wc))
3851 gtk_text_iter_forward_char(&iter);
3852 if (gtk_text_iter_ends_line(&iter))
3854 g_string_append_unichar(item_chars, wc);
3857 str = item_chars->str;
3858 g_string_free(item_chars, FALSE);
3862 /* return the number of spaces at a line's start */
3863 static int compose_left_offset_length(GtkTextBuffer *buffer,
3864 const GtkTextIter *start)
3866 GtkTextIter iter = *start;
3869 if (gtk_text_iter_ends_line(&iter))
3873 wc = gtk_text_iter_get_char(&iter);
3874 if (!g_unichar_isspace(wc))
3877 gtk_text_iter_forward_char(&iter);
3878 if (gtk_text_iter_ends_line(&iter))
3882 gtk_text_iter_forward_char(&iter);
3883 if (gtk_text_iter_ends_line(&iter))
3888 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3889 const GtkTextIter *start,
3890 GtkTextIter *break_pos,
3894 GtkTextIter iter = *start, line_end = *start;
3895 PangoLogAttr *attrs;
3902 gboolean can_break = FALSE;
3903 gboolean do_break = FALSE;
3904 gboolean was_white = FALSE;
3905 gboolean prev_dont_break = FALSE;
3907 gtk_text_iter_forward_to_line_end(&line_end);
3908 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3909 len = g_utf8_strlen(str, -1);
3913 g_warning("compose_get_line_break_pos: len = 0!\n");
3917 /* g_print("breaking line: %d: %s (len = %d)\n",
3918 gtk_text_iter_get_line(&iter), str, len); */
3920 attrs = g_new(PangoLogAttr, len + 1);
3922 pango_default_break(str, -1, NULL, attrs, len + 1);
3926 /* skip quote and leading spaces */
3927 for (i = 0; *p != '\0' && i < len; i++) {
3930 wc = g_utf8_get_char(p);
3931 if (i >= quote_len && !g_unichar_isspace(wc))
3933 if (g_unichar_iswide(wc))
3935 else if (*p == '\t')
3939 p = g_utf8_next_char(p);
3942 for (; *p != '\0' && i < len; i++) {
3943 PangoLogAttr *attr = attrs + i;
3947 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3950 was_white = attr->is_white;
3952 /* don't wrap URI */
3953 if ((uri_len = get_uri_len(p)) > 0) {
3955 if (pos > 0 && col > max_col) {
3965 wc = g_utf8_get_char(p);
3966 if (g_unichar_iswide(wc)) {
3968 if (prev_dont_break && can_break && attr->is_line_break)
3970 } else if (*p == '\t')
3974 if (pos > 0 && col > max_col) {
3979 if (*p == '-' || *p == '/')
3980 prev_dont_break = TRUE;
3982 prev_dont_break = FALSE;
3984 p = g_utf8_next_char(p);
3988 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3993 *break_pos = *start;
3994 gtk_text_iter_set_line_offset(break_pos, pos);
3999 static gboolean compose_join_next_line(Compose *compose,
4000 GtkTextBuffer *buffer,
4002 const gchar *quote_str)
4004 GtkTextIter iter_ = *iter, cur, prev, next, end;
4005 PangoLogAttr attrs[3];
4007 gchar *next_quote_str;
4010 gboolean keep_cursor = FALSE;
4012 if (!gtk_text_iter_forward_line(&iter_) ||
4013 gtk_text_iter_ends_line(&iter_)) {
4016 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4018 if ((quote_str || next_quote_str) &&
4019 strcmp2(quote_str, next_quote_str) != 0) {
4020 g_free(next_quote_str);
4023 g_free(next_quote_str);
4026 if (quote_len > 0) {
4027 gtk_text_iter_forward_chars(&end, quote_len);
4028 if (gtk_text_iter_ends_line(&end)) {
4033 /* don't join itemized lines */
4034 if (compose_itemized_length(buffer, &end) > 0) {
4038 /* don't join signature separator */
4039 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4042 /* delete quote str */
4044 gtk_text_buffer_delete(buffer, &iter_, &end);
4046 /* don't join line breaks put by the user */
4048 gtk_text_iter_backward_char(&cur);
4049 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4050 gtk_text_iter_forward_char(&cur);
4054 gtk_text_iter_forward_char(&cur);
4055 /* delete linebreak and extra spaces */
4056 while (gtk_text_iter_backward_char(&cur)) {
4057 wc1 = gtk_text_iter_get_char(&cur);
4058 if (!g_unichar_isspace(wc1))
4063 while (!gtk_text_iter_ends_line(&cur)) {
4064 wc1 = gtk_text_iter_get_char(&cur);
4065 if (!g_unichar_isspace(wc1))
4067 gtk_text_iter_forward_char(&cur);
4070 if (!gtk_text_iter_equal(&prev, &next)) {
4073 mark = gtk_text_buffer_get_insert(buffer);
4074 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4075 if (gtk_text_iter_equal(&prev, &cur))
4077 gtk_text_buffer_delete(buffer, &prev, &next);
4081 /* insert space if required */
4082 gtk_text_iter_backward_char(&prev);
4083 wc1 = gtk_text_iter_get_char(&prev);
4084 wc2 = gtk_text_iter_get_char(&next);
4085 gtk_text_iter_forward_char(&next);
4086 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4087 pango_default_break(str, -1, NULL, attrs, 3);
4088 if (!attrs[1].is_line_break ||
4089 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4090 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4092 gtk_text_iter_backward_char(&iter_);
4093 gtk_text_buffer_place_cursor(buffer, &iter_);
4102 #define ADD_TXT_POS(bp_, ep_, pti_) \
4103 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4104 last = last->next; \
4105 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4106 last->next = NULL; \
4108 g_warning("alloc error scanning URIs\n"); \
4111 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4113 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4114 GtkTextBuffer *buffer;
4115 GtkTextIter iter, break_pos, end_of_line;
4116 gchar *quote_str = NULL;
4118 gboolean wrap_quote = prefs_common.linewrap_quote;
4119 gboolean prev_autowrap = compose->autowrap;
4120 gint startq_offset = -1, noq_offset = -1;
4121 gint uri_start = -1, uri_stop = -1;
4122 gint nouri_start = -1, nouri_stop = -1;
4123 gint num_blocks = 0;
4124 gint quotelevel = -1;
4125 gboolean modified = force;
4126 gboolean removed = FALSE;
4127 gboolean modified_before_remove = FALSE;
4129 gboolean start = TRUE;
4130 gint itemized_len = 0, rem_item_len = 0;
4131 gchar *itemized_chars = NULL;
4132 gboolean item_continuation = FALSE;
4137 if (compose->draft_timeout_tag == -2) {
4141 compose->autowrap = FALSE;
4143 buffer = gtk_text_view_get_buffer(text);
4144 undo_wrapping(compose->undostruct, TRUE);
4149 mark = gtk_text_buffer_get_insert(buffer);
4150 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4154 if (compose->draft_timeout_tag == -2) {
4155 if (gtk_text_iter_ends_line(&iter)) {
4156 while (gtk_text_iter_ends_line(&iter) &&
4157 gtk_text_iter_forward_line(&iter))
4160 while (gtk_text_iter_backward_line(&iter)) {
4161 if (gtk_text_iter_ends_line(&iter)) {
4162 gtk_text_iter_forward_line(&iter);
4168 /* move to line start */
4169 gtk_text_iter_set_line_offset(&iter, 0);
4172 itemized_len = compose_itemized_length(buffer, &iter);
4174 if (!itemized_len) {
4175 itemized_len = compose_left_offset_length(buffer, &iter);
4176 item_continuation = TRUE;
4180 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4182 /* go until paragraph end (empty line) */
4183 while (start || !gtk_text_iter_ends_line(&iter)) {
4184 gchar *scanpos = NULL;
4185 /* parse table - in order of priority */
4187 const gchar *needle; /* token */
4189 /* token search function */
4190 gchar *(*search) (const gchar *haystack,
4191 const gchar *needle);
4192 /* part parsing function */
4193 gboolean (*parse) (const gchar *start,
4194 const gchar *scanpos,
4198 /* part to URI function */
4199 gchar *(*build_uri) (const gchar *bp,
4203 static struct table parser[] = {
4204 {"http://", strcasestr, get_uri_part, make_uri_string},
4205 {"https://", strcasestr, get_uri_part, make_uri_string},
4206 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4207 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4208 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4209 {"www.", strcasestr, get_uri_part, make_http_string},
4210 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4211 {"@", strcasestr, get_email_part, make_email_string}
4213 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4214 gint last_index = PARSE_ELEMS;
4216 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4220 if (!prev_autowrap && num_blocks == 0) {
4222 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4223 G_CALLBACK(text_inserted),
4226 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4229 uri_start = uri_stop = -1;
4231 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4234 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4235 if (startq_offset == -1)
4236 startq_offset = gtk_text_iter_get_offset(&iter);
4237 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4238 if (quotelevel > 2) {
4239 /* recycle colors */
4240 if (prefs_common.recycle_quote_colors)
4249 if (startq_offset == -1)
4250 noq_offset = gtk_text_iter_get_offset(&iter);
4254 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4257 if (gtk_text_iter_ends_line(&iter)) {
4259 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4260 prefs_common.linewrap_len,
4262 GtkTextIter prev, next, cur;
4263 if (prev_autowrap != FALSE || force) {
4264 compose->automatic_break = TRUE;
4266 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4267 compose->automatic_break = FALSE;
4268 if (itemized_len && compose->autoindent) {
4269 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4270 if (!item_continuation)
4271 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4273 } else if (quote_str && wrap_quote) {
4274 compose->automatic_break = TRUE;
4276 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4277 compose->automatic_break = FALSE;
4278 if (itemized_len && compose->autoindent) {
4279 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4280 if (!item_continuation)
4281 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4285 /* remove trailing spaces */
4287 rem_item_len = itemized_len;
4288 while (compose->autoindent && rem_item_len-- > 0)
4289 gtk_text_iter_backward_char(&cur);
4290 gtk_text_iter_backward_char(&cur);
4293 while (!gtk_text_iter_starts_line(&cur)) {
4296 gtk_text_iter_backward_char(&cur);
4297 wc = gtk_text_iter_get_char(&cur);
4298 if (!g_unichar_isspace(wc))
4302 if (!gtk_text_iter_equal(&prev, &next)) {
4303 gtk_text_buffer_delete(buffer, &prev, &next);
4305 gtk_text_iter_forward_char(&break_pos);
4309 gtk_text_buffer_insert(buffer, &break_pos,
4313 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4315 /* move iter to current line start */
4316 gtk_text_iter_set_line_offset(&iter, 0);
4323 /* move iter to next line start */
4329 if (!prev_autowrap && num_blocks > 0) {
4331 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4332 G_CALLBACK(text_inserted),
4336 while (!gtk_text_iter_ends_line(&end_of_line)) {
4337 gtk_text_iter_forward_char(&end_of_line);
4339 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4341 nouri_start = gtk_text_iter_get_offset(&iter);
4342 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4344 walk_pos = gtk_text_iter_get_offset(&iter);
4345 /* FIXME: this looks phony. scanning for anything in the parse table */
4346 for (n = 0; n < PARSE_ELEMS; n++) {
4349 tmp = parser[n].search(walk, parser[n].needle);
4351 if (scanpos == NULL || tmp < scanpos) {
4360 /* check if URI can be parsed */
4361 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4362 (const gchar **)&ep, FALSE)
4363 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4367 strlen(parser[last_index].needle);
4370 uri_start = walk_pos + (bp - o_walk);
4371 uri_stop = walk_pos + (ep - o_walk);
4375 gtk_text_iter_forward_line(&iter);
4378 if (startq_offset != -1) {
4379 GtkTextIter startquote, endquote;
4380 gtk_text_buffer_get_iter_at_offset(
4381 buffer, &startquote, startq_offset);
4384 switch (quotelevel) {
4386 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4387 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4388 gtk_text_buffer_apply_tag_by_name(
4389 buffer, "quote0", &startquote, &endquote);
4390 gtk_text_buffer_remove_tag_by_name(
4391 buffer, "quote1", &startquote, &endquote);
4392 gtk_text_buffer_remove_tag_by_name(
4393 buffer, "quote2", &startquote, &endquote);
4398 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4399 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4400 gtk_text_buffer_apply_tag_by_name(
4401 buffer, "quote1", &startquote, &endquote);
4402 gtk_text_buffer_remove_tag_by_name(
4403 buffer, "quote0", &startquote, &endquote);
4404 gtk_text_buffer_remove_tag_by_name(
4405 buffer, "quote2", &startquote, &endquote);
4410 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4411 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4412 gtk_text_buffer_apply_tag_by_name(
4413 buffer, "quote2", &startquote, &endquote);
4414 gtk_text_buffer_remove_tag_by_name(
4415 buffer, "quote0", &startquote, &endquote);
4416 gtk_text_buffer_remove_tag_by_name(
4417 buffer, "quote1", &startquote, &endquote);
4423 } else if (noq_offset != -1) {
4424 GtkTextIter startnoquote, endnoquote;
4425 gtk_text_buffer_get_iter_at_offset(
4426 buffer, &startnoquote, noq_offset);
4429 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4430 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4431 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4432 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4433 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4434 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4435 gtk_text_buffer_remove_tag_by_name(
4436 buffer, "quote0", &startnoquote, &endnoquote);
4437 gtk_text_buffer_remove_tag_by_name(
4438 buffer, "quote1", &startnoquote, &endnoquote);
4439 gtk_text_buffer_remove_tag_by_name(
4440 buffer, "quote2", &startnoquote, &endnoquote);
4446 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4447 GtkTextIter nouri_start_iter, nouri_end_iter;
4448 gtk_text_buffer_get_iter_at_offset(
4449 buffer, &nouri_start_iter, nouri_start);
4450 gtk_text_buffer_get_iter_at_offset(
4451 buffer, &nouri_end_iter, nouri_stop);
4452 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4453 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4454 gtk_text_buffer_remove_tag_by_name(
4455 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4456 modified_before_remove = modified;
4461 if (uri_start >= 0 && uri_stop > 0) {
4462 GtkTextIter uri_start_iter, uri_end_iter, back;
4463 gtk_text_buffer_get_iter_at_offset(
4464 buffer, &uri_start_iter, uri_start);
4465 gtk_text_buffer_get_iter_at_offset(
4466 buffer, &uri_end_iter, uri_stop);
4467 back = uri_end_iter;
4468 gtk_text_iter_backward_char(&back);
4469 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4470 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4471 gtk_text_buffer_apply_tag_by_name(
4472 buffer, "link", &uri_start_iter, &uri_end_iter);
4474 if (removed && !modified_before_remove) {
4480 // debug_print("not modified, out after %d lines\n", lines);
4484 // debug_print("modified, out after %d lines\n", lines);
4486 g_free(itemized_chars);
4489 undo_wrapping(compose->undostruct, FALSE);
4490 compose->autowrap = prev_autowrap;
4495 void compose_action_cb(void *data)
4497 Compose *compose = (Compose *)data;
4498 compose_wrap_all(compose);
4501 static void compose_wrap_all(Compose *compose)
4503 compose_wrap_all_full(compose, FALSE);
4506 static void compose_wrap_all_full(Compose *compose, gboolean force)
4508 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4509 GtkTextBuffer *buffer;
4511 gboolean modified = TRUE;
4513 buffer = gtk_text_view_get_buffer(text);
4515 gtk_text_buffer_get_start_iter(buffer, &iter);
4516 while (!gtk_text_iter_is_end(&iter) && modified)
4517 modified = compose_beautify_paragraph(compose, &iter, force);
4521 static void compose_set_title(Compose *compose)
4527 edited = compose->modified ? _(" [Edited]") : "";
4529 subject = gtk_editable_get_chars(
4530 GTK_EDITABLE(compose->subject_entry), 0, -1);
4532 #ifndef GENERIC_UMPC
4533 if (subject && strlen(subject))
4534 str = g_strdup_printf(_("%s - Compose message%s"),
4537 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4539 str = g_strdup(_("Compose message"));
4542 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4548 * compose_current_mail_account:
4550 * Find a current mail account (the currently selected account, or the
4551 * default account, if a news account is currently selected). If a
4552 * mail account cannot be found, display an error message.
4554 * Return value: Mail account, or NULL if not found.
4556 static PrefsAccount *
4557 compose_current_mail_account(void)
4561 if (cur_account && cur_account->protocol != A_NNTP)
4564 ac = account_get_default();
4565 if (!ac || ac->protocol == A_NNTP) {
4566 alertpanel_error(_("Account for sending mail is not specified.\n"
4567 "Please select a mail account before sending."));
4574 #define QUOTE_IF_REQUIRED(out, str) \
4576 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4580 len = strlen(str) + 3; \
4581 if ((__tmp = alloca(len)) == NULL) { \
4582 g_warning("can't allocate memory\n"); \
4583 g_string_free(header, TRUE); \
4586 g_snprintf(__tmp, len, "\"%s\"", str); \
4591 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4592 g_warning("can't allocate memory\n"); \
4593 g_string_free(header, TRUE); \
4596 strcpy(__tmp, str); \
4602 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4604 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4608 len = strlen(str) + 3; \
4609 if ((__tmp = alloca(len)) == NULL) { \
4610 g_warning("can't allocate memory\n"); \
4613 g_snprintf(__tmp, len, "\"%s\"", str); \
4618 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4619 g_warning("can't allocate memory\n"); \
4622 strcpy(__tmp, str); \
4628 static void compose_select_account(Compose *compose, PrefsAccount *account,
4631 gchar *from = NULL, *header;
4632 ComposeHeaderEntry *header_entry;
4634 cm_return_if_fail(account != NULL);
4636 compose->account = account;
4637 if (account->name && *account->name) {
4639 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4640 from = g_strdup_printf("%s <%s>",
4641 buf, account->address);
4642 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4644 from = g_strdup_printf("<%s>",
4646 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4651 compose_set_title(compose);
4653 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4654 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4656 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4657 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4658 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4660 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4662 activate_privacy_system(compose, account, FALSE);
4664 if (!init && compose->mode != COMPOSE_REDIRECT) {
4665 undo_block(compose->undostruct);
4666 compose_insert_sig(compose, TRUE);
4667 undo_unblock(compose->undostruct);
4670 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4671 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4673 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4674 if (account->protocol == A_NNTP) {
4675 if (!strcmp(header, _("To:")))
4676 combobox_select_by_text(
4677 GTK_COMBO_BOX(header_entry->combo),
4680 if (!strcmp(header, _("Newsgroups:")))
4681 combobox_select_by_text(
4682 GTK_COMBO_BOX(header_entry->combo),
4690 /* use account's dict info if set */
4691 if (compose->gtkaspell) {
4692 if (account->enable_default_dictionary)
4693 gtkaspell_change_dict(compose->gtkaspell,
4694 account->default_dictionary, FALSE);
4695 if (account->enable_default_alt_dictionary)
4696 gtkaspell_change_alt_dict(compose->gtkaspell,
4697 account->default_alt_dictionary);
4698 if (account->enable_default_dictionary
4699 || account->enable_default_alt_dictionary)
4700 compose_spell_menu_changed(compose);
4705 gboolean compose_check_for_valid_recipient(Compose *compose) {
4706 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4707 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4708 gboolean recipient_found = FALSE;
4712 /* free to and newsgroup list */
4713 slist_free_strings(compose->to_list);
4714 g_slist_free(compose->to_list);
4715 compose->to_list = NULL;
4717 slist_free_strings(compose->newsgroup_list);
4718 g_slist_free(compose->newsgroup_list);
4719 compose->newsgroup_list = NULL;
4721 /* search header entries for to and newsgroup entries */
4722 for (list = compose->header_list; list; list = list->next) {
4725 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4726 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4729 if (entry[0] != '\0') {
4730 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4731 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4732 compose->to_list = address_list_append(compose->to_list, entry);
4733 recipient_found = TRUE;
4736 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4737 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4738 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4739 recipient_found = TRUE;
4746 return recipient_found;
4749 static gboolean compose_check_for_set_recipients(Compose *compose)
4751 if (compose->account->set_autocc && compose->account->auto_cc) {
4752 gboolean found_other = FALSE;
4754 /* search header entries for to and newsgroup entries */
4755 for (list = compose->header_list; list; list = list->next) {
4758 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4759 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4762 if (strcmp(entry, compose->account->auto_cc)
4763 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4773 if (compose->batch) {
4774 gtk_widget_show_all(compose->window);
4776 aval = alertpanel(_("Send"),
4777 _("The only recipient is the default CC address. Send anyway?"),
4778 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4779 if (aval != G_ALERTALTERNATE)
4783 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4784 gboolean found_other = FALSE;
4786 /* search header entries for to and newsgroup entries */
4787 for (list = compose->header_list; list; list = list->next) {
4790 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4791 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4794 if (strcmp(entry, compose->account->auto_bcc)
4795 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4805 if (compose->batch) {
4806 gtk_widget_show_all(compose->window);
4808 aval = alertpanel(_("Send"),
4809 _("The only recipient is the default BCC address. Send anyway?"),
4810 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4811 if (aval != G_ALERTALTERNATE)
4818 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4822 if (compose_check_for_valid_recipient(compose) == FALSE) {
4823 if (compose->batch) {
4824 gtk_widget_show_all(compose->window);
4826 alertpanel_error(_("Recipient is not specified."));
4830 if (compose_check_for_set_recipients(compose) == FALSE) {
4834 if (!compose->batch) {
4835 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4836 if (*str == '\0' && check_everything == TRUE &&
4837 compose->mode != COMPOSE_REDIRECT) {
4839 gchar *button_label;
4842 if (compose->sending)
4843 button_label = _("+_Send");
4845 button_label = _("+_Queue");
4846 message = g_strdup_printf(_("Subject is empty. %s"),
4847 compose->sending?_("Send it anyway?"):
4848 _("Queue it anyway?"));
4850 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4851 GTK_STOCK_CANCEL, button_label, NULL);
4853 if (aval != G_ALERTALTERNATE)
4858 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4864 gint compose_send(Compose *compose)
4867 FolderItem *folder = NULL;
4869 gchar *msgpath = NULL;
4870 gboolean discard_window = FALSE;
4871 gchar *errstr = NULL;
4872 gchar *tmsgid = NULL;
4873 MainWindow *mainwin = mainwindow_get_mainwindow();
4874 gboolean queued_removed = FALSE;
4876 if (prefs_common.send_dialog_invisible
4877 || compose->batch == TRUE)
4878 discard_window = TRUE;
4880 compose_allow_user_actions (compose, FALSE);
4881 compose->sending = TRUE;
4883 if (compose_check_entries(compose, TRUE) == FALSE) {
4884 if (compose->batch) {
4885 gtk_widget_show_all(compose->window);
4891 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4894 if (compose->batch) {
4895 gtk_widget_show_all(compose->window);
4898 alertpanel_error(_("Could not queue message for sending:\n\n"
4899 "Charset conversion failed."));
4900 } else if (val == -5) {
4901 alertpanel_error(_("Could not queue message for sending:\n\n"
4902 "Couldn't get recipient encryption key."));
4903 } else if (val == -6) {
4905 } else if (val == -3) {
4906 if (privacy_peek_error())
4907 alertpanel_error(_("Could not queue message for sending:\n\n"
4908 "Signature failed: %s"), privacy_get_error());
4909 } else if (val == -2 && errno != 0) {
4910 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4912 alertpanel_error(_("Could not queue message for sending."));
4917 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4918 if (discard_window) {
4919 compose->sending = FALSE;
4920 compose_close(compose);
4921 /* No more compose access in the normal codepath
4922 * after this point! */
4927 alertpanel_error(_("The message was queued but could not be "
4928 "sent.\nUse \"Send queued messages\" from "
4929 "the main window to retry."));
4930 if (!discard_window) {
4937 if (msgpath == NULL) {
4938 msgpath = folder_item_fetch_msg(folder, msgnum);
4939 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4942 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4943 claws_unlink(msgpath);
4946 if (!discard_window) {
4948 if (!queued_removed)
4949 folder_item_remove_msg(folder, msgnum);
4950 folder_item_scan(folder);
4952 /* make sure we delete that */
4953 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4955 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4956 folder_item_remove_msg(folder, tmp->msgnum);
4957 procmsg_msginfo_free(tmp);
4964 if (!queued_removed)
4965 folder_item_remove_msg(folder, msgnum);
4966 folder_item_scan(folder);
4968 /* make sure we delete that */
4969 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4971 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4972 folder_item_remove_msg(folder, tmp->msgnum);
4973 procmsg_msginfo_free(tmp);
4976 if (!discard_window) {
4977 compose->sending = FALSE;
4978 compose_allow_user_actions (compose, TRUE);
4979 compose_close(compose);
4983 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4984 "the main window to retry."), errstr);
4987 alertpanel_error_log(_("The message was queued but could not be "
4988 "sent.\nUse \"Send queued messages\" from "
4989 "the main window to retry."));
4991 if (!discard_window) {
5000 toolbar_main_set_sensitive(mainwin);
5001 main_window_set_menu_sensitive(mainwin);
5007 compose_allow_user_actions (compose, TRUE);
5008 compose->sending = FALSE;
5009 compose->modified = TRUE;
5010 toolbar_main_set_sensitive(mainwin);
5011 main_window_set_menu_sensitive(mainwin);
5016 static gboolean compose_use_attach(Compose *compose)
5018 GtkTreeModel *model = gtk_tree_view_get_model
5019 (GTK_TREE_VIEW(compose->attach_clist));
5020 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5023 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5026 gchar buf[BUFFSIZE];
5028 gboolean first_to_address;
5029 gboolean first_cc_address;
5031 ComposeHeaderEntry *headerentry;
5032 const gchar *headerentryname;
5033 const gchar *cc_hdr;
5034 const gchar *to_hdr;
5035 gboolean err = FALSE;
5037 debug_print("Writing redirect header\n");
5039 cc_hdr = prefs_common_translated_header_name("Cc:");
5040 to_hdr = prefs_common_translated_header_name("To:");
5042 first_to_address = TRUE;
5043 for (list = compose->header_list; list; list = list->next) {
5044 headerentry = ((ComposeHeaderEntry *)list->data);
5045 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5047 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5048 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5049 Xstrdup_a(str, entstr, return -1);
5051 if (str[0] != '\0') {
5052 compose_convert_header
5053 (compose, buf, sizeof(buf), str,
5054 strlen("Resent-To") + 2, TRUE);
5056 if (first_to_address) {
5057 err |= (fprintf(fp, "Resent-To: ") < 0);
5058 first_to_address = FALSE;
5060 err |= (fprintf(fp, ",") < 0);
5062 err |= (fprintf(fp, "%s", buf) < 0);
5066 if (!first_to_address) {
5067 err |= (fprintf(fp, "\n") < 0);
5070 first_cc_address = TRUE;
5071 for (list = compose->header_list; list; list = list->next) {
5072 headerentry = ((ComposeHeaderEntry *)list->data);
5073 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5075 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5076 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5077 Xstrdup_a(str, strg, return -1);
5079 if (str[0] != '\0') {
5080 compose_convert_header
5081 (compose, buf, sizeof(buf), str,
5082 strlen("Resent-Cc") + 2, TRUE);
5084 if (first_cc_address) {
5085 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5086 first_cc_address = FALSE;
5088 err |= (fprintf(fp, ",") < 0);
5090 err |= (fprintf(fp, "%s", buf) < 0);
5094 if (!first_cc_address) {
5095 err |= (fprintf(fp, "\n") < 0);
5098 return (err ? -1:0);
5101 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5103 gchar buf[BUFFSIZE];
5105 const gchar *entstr;
5106 /* struct utsname utsbuf; */
5107 gboolean err = FALSE;
5109 cm_return_val_if_fail(fp != NULL, -1);
5110 cm_return_val_if_fail(compose->account != NULL, -1);
5111 cm_return_val_if_fail(compose->account->address != NULL, -1);
5114 get_rfc822_date(buf, sizeof(buf));
5115 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5118 if (compose->account->name && *compose->account->name) {
5119 compose_convert_header
5120 (compose, buf, sizeof(buf), compose->account->name,
5121 strlen("From: "), TRUE);
5122 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5123 buf, compose->account->address) < 0);
5125 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5128 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5129 if (*entstr != '\0') {
5130 Xstrdup_a(str, entstr, return -1);
5133 compose_convert_header(compose, buf, sizeof(buf), str,
5134 strlen("Subject: "), FALSE);
5135 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5139 /* Resent-Message-ID */
5140 if (compose->account->set_domain && compose->account->domain) {
5141 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5142 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5143 g_snprintf(buf, sizeof(buf), "%s",
5144 strchr(compose->account->address, '@') ?
5145 strchr(compose->account->address, '@')+1 :
5146 compose->account->address);
5148 g_snprintf(buf, sizeof(buf), "%s", "");
5151 if (compose->account->gen_msgid) {
5153 if (compose->account->msgid_with_addr) {
5154 addr = compose->account->address;
5156 generate_msgid(buf, sizeof(buf), addr);
5157 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5158 compose->msgid = g_strdup(buf);
5160 compose->msgid = NULL;
5163 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5166 /* separator between header and body */
5167 err |= (fputs("\n", fp) == EOF);
5169 return (err ? -1:0);
5172 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5176 gchar buf[BUFFSIZE];
5178 gboolean skip = FALSE;
5179 gboolean err = FALSE;
5180 gchar *not_included[]={
5181 "Return-Path:", "Delivered-To:", "Received:",
5182 "Subject:", "X-UIDL:", "AF:",
5183 "NF:", "PS:", "SRH:",
5184 "SFN:", "DSR:", "MID:",
5185 "CFG:", "PT:", "S:",
5186 "RQ:", "SSV:", "NSV:",
5187 "SSH:", "R:", "MAID:",
5188 "NAID:", "RMID:", "FMID:",
5189 "SCF:", "RRCPT:", "NG:",
5190 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5191 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5192 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5193 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5194 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5197 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5198 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5202 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5204 for (i = 0; not_included[i] != NULL; i++) {
5205 if (g_ascii_strncasecmp(buf, not_included[i],
5206 strlen(not_included[i])) == 0) {
5213 if (fputs(buf, fdest) == -1)
5216 if (!prefs_common.redirect_keep_from) {
5217 if (g_ascii_strncasecmp(buf, "From:",
5218 strlen("From:")) == 0) {
5219 err |= (fputs(" (by way of ", fdest) == EOF);
5220 if (compose->account->name
5221 && *compose->account->name) {
5222 compose_convert_header
5223 (compose, buf, sizeof(buf),
5224 compose->account->name,
5227 err |= (fprintf(fdest, "%s <%s>",
5229 compose->account->address) < 0);
5231 err |= (fprintf(fdest, "%s",
5232 compose->account->address) < 0);
5233 err |= (fputs(")", fdest) == EOF);
5237 if (fputs("\n", fdest) == -1)
5244 if (compose_redirect_write_headers(compose, fdest))
5247 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5248 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5261 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5263 GtkTextBuffer *buffer;
5264 GtkTextIter start, end;
5267 const gchar *out_codeset;
5268 EncodingType encoding = ENC_UNKNOWN;
5269 MimeInfo *mimemsg, *mimetext;
5271 const gchar *src_codeset = CS_INTERNAL;
5272 gchar *from_addr = NULL;
5273 gchar *from_name = NULL;
5275 if (action == COMPOSE_WRITE_FOR_SEND)
5276 attach_parts = TRUE;
5278 /* create message MimeInfo */
5279 mimemsg = procmime_mimeinfo_new();
5280 mimemsg->type = MIMETYPE_MESSAGE;
5281 mimemsg->subtype = g_strdup("rfc822");
5282 mimemsg->content = MIMECONTENT_MEM;
5283 mimemsg->tmp = TRUE; /* must free content later */
5284 mimemsg->data.mem = compose_get_header(compose);
5286 /* Create text part MimeInfo */
5287 /* get all composed text */
5288 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5289 gtk_text_buffer_get_start_iter(buffer, &start);
5290 gtk_text_buffer_get_end_iter(buffer, &end);
5291 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5293 out_codeset = conv_get_charset_str(compose->out_encoding);
5295 if (!out_codeset && is_ascii_str(chars)) {
5296 out_codeset = CS_US_ASCII;
5297 } else if (prefs_common.outgoing_fallback_to_ascii &&
5298 is_ascii_str(chars)) {
5299 out_codeset = CS_US_ASCII;
5300 encoding = ENC_7BIT;
5304 gchar *test_conv_global_out = NULL;
5305 gchar *test_conv_reply = NULL;
5307 /* automatic mode. be automatic. */
5308 codeconv_set_strict(TRUE);
5310 out_codeset = conv_get_outgoing_charset_str();
5312 debug_print("trying to convert to %s\n", out_codeset);
5313 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5316 if (!test_conv_global_out && compose->orig_charset
5317 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5318 out_codeset = compose->orig_charset;
5319 debug_print("failure; trying to convert to %s\n", out_codeset);
5320 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5323 if (!test_conv_global_out && !test_conv_reply) {
5325 out_codeset = CS_INTERNAL;
5326 debug_print("failure; finally using %s\n", out_codeset);
5328 g_free(test_conv_global_out);
5329 g_free(test_conv_reply);
5330 codeconv_set_strict(FALSE);
5333 if (encoding == ENC_UNKNOWN) {
5334 if (prefs_common.encoding_method == CTE_BASE64)
5335 encoding = ENC_BASE64;
5336 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5337 encoding = ENC_QUOTED_PRINTABLE;
5338 else if (prefs_common.encoding_method == CTE_8BIT)
5339 encoding = ENC_8BIT;
5341 encoding = procmime_get_encoding_for_charset(out_codeset);
5344 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5345 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5347 if (action == COMPOSE_WRITE_FOR_SEND) {
5348 codeconv_set_strict(TRUE);
5349 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5350 codeconv_set_strict(FALSE);
5356 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5357 "to the specified %s charset.\n"
5358 "Send it as %s?"), out_codeset, src_codeset);
5359 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5360 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5363 if (aval != G_ALERTALTERNATE) {
5368 out_codeset = src_codeset;
5374 out_codeset = src_codeset;
5379 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5380 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5381 strstr(buf, "\nFrom ") != NULL) {
5382 encoding = ENC_QUOTED_PRINTABLE;
5386 mimetext = procmime_mimeinfo_new();
5387 mimetext->content = MIMECONTENT_MEM;
5388 mimetext->tmp = TRUE; /* must free content later */
5389 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5390 * and free the data, which we need later. */
5391 mimetext->data.mem = g_strdup(buf);
5392 mimetext->type = MIMETYPE_TEXT;
5393 mimetext->subtype = g_strdup("plain");
5394 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5395 g_strdup(out_codeset));
5397 /* protect trailing spaces when signing message */
5398 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5399 privacy_system_can_sign(compose->privacy_system)) {
5400 encoding = ENC_QUOTED_PRINTABLE;
5403 debug_print("main text: %zd bytes encoded as %s in %d\n",
5404 strlen(buf), out_codeset, encoding);
5406 /* check for line length limit */
5407 if (action == COMPOSE_WRITE_FOR_SEND &&
5408 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5409 check_line_length(buf, 1000, &line) < 0) {
5413 msg = g_strdup_printf
5414 (_("Line %d exceeds the line length limit (998 bytes).\n"
5415 "The contents of the message might be broken on the way to the delivery.\n"
5417 "Send it anyway?"), line + 1);
5418 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5420 if (aval != G_ALERTALTERNATE) {
5426 if (encoding != ENC_UNKNOWN)
5427 procmime_encode_content(mimetext, encoding);
5429 /* append attachment parts */
5430 if (compose_use_attach(compose) && attach_parts) {
5431 MimeInfo *mimempart;
5432 gchar *boundary = NULL;
5433 mimempart = procmime_mimeinfo_new();
5434 mimempart->content = MIMECONTENT_EMPTY;
5435 mimempart->type = MIMETYPE_MULTIPART;
5436 mimempart->subtype = g_strdup("mixed");
5440 boundary = generate_mime_boundary(NULL);
5441 } while (strstr(buf, boundary) != NULL);
5443 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5446 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5448 g_node_append(mimempart->node, mimetext->node);
5449 g_node_append(mimemsg->node, mimempart->node);
5451 if (compose_add_attachments(compose, mimempart) < 0)
5454 g_node_append(mimemsg->node, mimetext->node);
5458 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5459 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5460 /* extract name and address */
5461 if (strstr(spec, " <") && strstr(spec, ">")) {
5462 from_addr = g_strdup(strrchr(spec, '<')+1);
5463 *(strrchr(from_addr, '>')) = '\0';
5464 from_name = g_strdup(spec);
5465 *(strrchr(from_name, '<')) = '\0';
5472 /* sign message if sending */
5473 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5474 privacy_system_can_sign(compose->privacy_system))
5475 if (!privacy_sign(compose->privacy_system, mimemsg,
5476 compose->account, from_addr)) {
5483 procmime_write_mimeinfo(mimemsg, fp);
5485 procmime_mimeinfo_free_all(mimemsg);
5490 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5492 GtkTextBuffer *buffer;
5493 GtkTextIter start, end;
5498 if ((fp = g_fopen(file, "wb")) == NULL) {
5499 FILE_OP_ERROR(file, "fopen");
5503 /* chmod for security */
5504 if (change_file_mode_rw(fp, file) < 0) {
5505 FILE_OP_ERROR(file, "chmod");
5506 g_warning("can't change file mode\n");
5509 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5510 gtk_text_buffer_get_start_iter(buffer, &start);
5511 gtk_text_buffer_get_end_iter(buffer, &end);
5512 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5514 chars = conv_codeset_strdup
5515 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5518 if (!chars) return -1;
5521 len = strlen(chars);
5522 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5523 FILE_OP_ERROR(file, "fwrite");
5532 if (fclose(fp) == EOF) {
5533 FILE_OP_ERROR(file, "fclose");
5540 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5543 MsgInfo *msginfo = compose->targetinfo;
5545 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5546 if (!msginfo) return -1;
5548 if (!force && MSG_IS_LOCKED(msginfo->flags))
5551 item = msginfo->folder;
5552 cm_return_val_if_fail(item != NULL, -1);
5554 if (procmsg_msg_exist(msginfo) &&
5555 (folder_has_parent_of_type(item, F_QUEUE) ||
5556 folder_has_parent_of_type(item, F_DRAFT)
5557 || msginfo == compose->autosaved_draft)) {
5558 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5559 g_warning("can't remove the old message\n");
5562 debug_print("removed reedit target %d\n", msginfo->msgnum);
5569 static void compose_remove_draft(Compose *compose)
5572 MsgInfo *msginfo = compose->targetinfo;
5573 drafts = account_get_special_folder(compose->account, F_DRAFT);
5575 if (procmsg_msg_exist(msginfo)) {
5576 folder_item_remove_msg(drafts, msginfo->msgnum);
5581 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5582 gboolean remove_reedit_target)
5584 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5587 static gboolean compose_warn_encryption(Compose *compose)
5589 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5590 AlertValue val = G_ALERTALTERNATE;
5592 if (warning == NULL)
5595 val = alertpanel_full(_("Encryption warning"), warning,
5596 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5597 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5598 if (val & G_ALERTDISABLE) {
5599 val &= ~G_ALERTDISABLE;
5600 if (val == G_ALERTALTERNATE)
5601 privacy_inhibit_encrypt_warning(compose->privacy_system,
5605 if (val == G_ALERTALTERNATE) {
5612 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5613 gchar **msgpath, gboolean check_subject,
5614 gboolean remove_reedit_target)
5621 static gboolean lock = FALSE;
5622 PrefsAccount *mailac = NULL, *newsac = NULL;
5623 gboolean err = FALSE;
5625 debug_print("queueing message...\n");
5626 cm_return_val_if_fail(compose->account != NULL, -1);
5630 if (compose_check_entries(compose, check_subject) == FALSE) {
5632 if (compose->batch) {
5633 gtk_widget_show_all(compose->window);
5638 if (!compose->to_list && !compose->newsgroup_list) {
5639 g_warning("can't get recipient list.");
5644 if (compose->to_list) {
5645 if (compose->account->protocol != A_NNTP)
5646 mailac = compose->account;
5647 else if (cur_account && cur_account->protocol != A_NNTP)
5648 mailac = cur_account;
5649 else if (!(mailac = compose_current_mail_account())) {
5651 alertpanel_error(_("No account for sending mails available!"));
5656 if (compose->newsgroup_list) {
5657 if (compose->account->protocol == A_NNTP)
5658 newsac = compose->account;
5661 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5666 /* write queue header */
5667 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5668 G_DIR_SEPARATOR, compose, (guint) rand());
5669 debug_print("queuing to %s\n", tmp);
5670 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5671 FILE_OP_ERROR(tmp, "fopen");
5677 if (change_file_mode_rw(fp, tmp) < 0) {
5678 FILE_OP_ERROR(tmp, "chmod");
5679 g_warning("can't change file mode\n");
5682 /* queueing variables */
5683 err |= (fprintf(fp, "AF:\n") < 0);
5684 err |= (fprintf(fp, "NF:0\n") < 0);
5685 err |= (fprintf(fp, "PS:10\n") < 0);
5686 err |= (fprintf(fp, "SRH:1\n") < 0);
5687 err |= (fprintf(fp, "SFN:\n") < 0);
5688 err |= (fprintf(fp, "DSR:\n") < 0);
5690 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5692 err |= (fprintf(fp, "MID:\n") < 0);
5693 err |= (fprintf(fp, "CFG:\n") < 0);
5694 err |= (fprintf(fp, "PT:0\n") < 0);
5695 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5696 err |= (fprintf(fp, "RQ:\n") < 0);
5698 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5700 err |= (fprintf(fp, "SSV:\n") < 0);
5702 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5704 err |= (fprintf(fp, "NSV:\n") < 0);
5705 err |= (fprintf(fp, "SSH:\n") < 0);
5706 /* write recepient list */
5707 if (compose->to_list) {
5708 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5709 for (cur = compose->to_list->next; cur != NULL;
5711 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5712 err |= (fprintf(fp, "\n") < 0);
5714 /* write newsgroup list */
5715 if (compose->newsgroup_list) {
5716 err |= (fprintf(fp, "NG:") < 0);
5717 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5718 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5719 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5720 err |= (fprintf(fp, "\n") < 0);
5722 /* Sylpheed account IDs */
5724 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5726 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5729 if (compose->privacy_system != NULL) {
5730 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5731 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5732 if (compose->use_encryption) {
5734 if (!compose_warn_encryption(compose)) {
5741 if (mailac && mailac->encrypt_to_self) {
5742 GSList *tmp_list = g_slist_copy(compose->to_list);
5743 tmp_list = g_slist_append(tmp_list, compose->account->address);
5744 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5745 g_slist_free(tmp_list);
5747 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5749 if (encdata != NULL) {
5750 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5751 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5752 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5754 } /* else we finally dont want to encrypt */
5756 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5757 /* and if encdata was null, it means there's been a problem in
5769 /* Save copy folder */
5770 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5771 gchar *savefolderid;
5773 savefolderid = compose_get_save_to(compose);
5774 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5775 g_free(savefolderid);
5777 /* Save copy folder */
5778 if (compose->return_receipt) {
5779 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5781 /* Message-ID of message replying to */
5782 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5785 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5786 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5789 /* Message-ID of message forwarding to */
5790 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5793 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5794 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5798 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5799 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5801 /* end of headers */
5802 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5804 if (compose->redirect_filename != NULL) {
5805 if (compose_redirect_write_to_file(compose, fp) < 0) {
5814 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5819 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5823 g_warning("failed to write queue message\n");
5830 if (fclose(fp) == EOF) {
5831 FILE_OP_ERROR(tmp, "fclose");
5838 if (item && *item) {
5841 queue = account_get_special_folder(compose->account, F_QUEUE);
5844 g_warning("can't find queue folder\n");
5850 folder_item_scan(queue);
5851 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5852 g_warning("can't queue the message\n");
5859 if (msgpath == NULL) {
5865 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5866 compose_remove_reedit_target(compose, FALSE);
5869 if ((msgnum != NULL) && (item != NULL)) {
5877 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
5880 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5882 struct stat statbuf;
5883 gchar *type, *subtype;
5884 GtkTreeModel *model;
5887 model = gtk_tree_view_get_model(tree_view);
5889 if (!gtk_tree_model_get_iter_first(model, &iter))
5892 gtk_tree_model_get(model, &iter,
5896 if (!is_file_exist(ainfo->file)) {
5897 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
5898 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
5899 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
5901 if (val == G_ALERTDEFAULT) {
5906 mimepart = procmime_mimeinfo_new();
5907 mimepart->content = MIMECONTENT_FILE;
5908 mimepart->data.filename = g_strdup(ainfo->file);
5909 mimepart->tmp = FALSE; /* or we destroy our attachment */
5910 mimepart->offset = 0;
5912 g_stat(ainfo->file, &statbuf);
5913 mimepart->length = statbuf.st_size;
5915 type = g_strdup(ainfo->content_type);
5917 if (!strchr(type, '/')) {
5919 type = g_strdup("application/octet-stream");
5922 subtype = strchr(type, '/') + 1;
5923 *(subtype - 1) = '\0';
5924 mimepart->type = procmime_get_media_type(type);
5925 mimepart->subtype = g_strdup(subtype);
5928 if (mimepart->type == MIMETYPE_MESSAGE &&
5929 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5930 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5933 if (mimepart->type == MIMETYPE_APPLICATION &&
5934 !strcmp2(mimepart->subtype, "octet-stream"))
5935 g_hash_table_insert(mimepart->typeparameters,
5936 g_strdup("name"), g_strdup(ainfo->name));
5937 g_hash_table_insert(mimepart->dispositionparameters,
5938 g_strdup("filename"), g_strdup(ainfo->name));
5939 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5943 if (compose->use_signing) {
5944 if (ainfo->encoding == ENC_7BIT)
5945 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5946 else if (ainfo->encoding == ENC_8BIT)
5947 ainfo->encoding = ENC_BASE64;
5950 procmime_encode_content(mimepart, ainfo->encoding);
5952 g_node_append(parent->node, mimepart->node);
5953 } while (gtk_tree_model_iter_next(model, &iter));
5958 #define IS_IN_CUSTOM_HEADER(header) \
5959 (compose->account->add_customhdr && \
5960 custom_header_find(compose->account->customhdr_list, header) != NULL)
5962 static void compose_add_headerfield_from_headerlist(Compose *compose,
5964 const gchar *fieldname,
5965 const gchar *seperator)
5967 gchar *str, *fieldname_w_colon;
5968 gboolean add_field = FALSE;
5970 ComposeHeaderEntry *headerentry;
5971 const gchar *headerentryname;
5972 const gchar *trans_fieldname;
5975 if (IS_IN_CUSTOM_HEADER(fieldname))
5978 debug_print("Adding %s-fields\n", fieldname);
5980 fieldstr = g_string_sized_new(64);
5982 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5983 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5985 for (list = compose->header_list; list; list = list->next) {
5986 headerentry = ((ComposeHeaderEntry *)list->data);
5987 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5989 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5990 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5992 if (str[0] != '\0') {
5994 g_string_append(fieldstr, seperator);
5995 g_string_append(fieldstr, str);
6004 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6005 compose_convert_header
6006 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6007 strlen(fieldname) + 2, TRUE);
6008 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6012 g_free(fieldname_w_colon);
6013 g_string_free(fieldstr, TRUE);
6018 static gchar *compose_get_header(Compose *compose)
6020 gchar buf[BUFFSIZE];
6021 const gchar *entry_str;
6025 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6027 gchar *from_name = NULL, *from_address = NULL;
6030 cm_return_val_if_fail(compose->account != NULL, NULL);
6031 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6033 header = g_string_sized_new(64);
6036 get_rfc822_date(buf, sizeof(buf));
6037 g_string_append_printf(header, "Date: %s\n", buf);
6041 if (compose->account->name && *compose->account->name) {
6043 QUOTE_IF_REQUIRED(buf, compose->account->name);
6044 tmp = g_strdup_printf("%s <%s>",
6045 buf, compose->account->address);
6047 tmp = g_strdup_printf("%s",
6048 compose->account->address);
6050 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6051 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6053 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6054 from_address = g_strdup(compose->account->address);
6056 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6057 /* extract name and address */
6058 if (strstr(spec, " <") && strstr(spec, ">")) {
6059 from_address = g_strdup(strrchr(spec, '<')+1);
6060 *(strrchr(from_address, '>')) = '\0';
6061 from_name = g_strdup(spec);
6062 *(strrchr(from_name, '<')) = '\0';
6065 from_address = g_strdup(spec);
6072 if (from_name && *from_name) {
6073 compose_convert_header
6074 (compose, buf, sizeof(buf), from_name,
6075 strlen("From: "), TRUE);
6076 QUOTE_IF_REQUIRED(name, buf);
6078 g_string_append_printf(header, "From: %s <%s>\n",
6079 name, from_address);
6081 g_string_append_printf(header, "From: %s\n", from_address);
6084 g_free(from_address);
6087 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6090 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6093 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6096 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6099 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6101 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6104 compose_convert_header(compose, buf, sizeof(buf), str,
6105 strlen("Subject: "), FALSE);
6106 g_string_append_printf(header, "Subject: %s\n", buf);
6112 if (compose->account->set_domain && compose->account->domain) {
6113 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6114 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6115 g_snprintf(buf, sizeof(buf), "%s",
6116 strchr(compose->account->address, '@') ?
6117 strchr(compose->account->address, '@')+1 :
6118 compose->account->address);
6120 g_snprintf(buf, sizeof(buf), "%s", "");
6123 if (compose->account->gen_msgid) {
6125 if (compose->account->msgid_with_addr) {
6126 addr = compose->account->address;
6128 generate_msgid(buf, sizeof(buf), addr);
6129 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6130 compose->msgid = g_strdup(buf);
6132 compose->msgid = NULL;
6135 if (compose->remove_references == FALSE) {
6137 if (compose->inreplyto && compose->to_list)
6138 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6141 if (compose->references)
6142 g_string_append_printf(header, "References: %s\n", compose->references);
6146 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6149 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6152 if (compose->account->organization &&
6153 strlen(compose->account->organization) &&
6154 !IS_IN_CUSTOM_HEADER("Organization")) {
6155 compose_convert_header(compose, buf, sizeof(buf),
6156 compose->account->organization,
6157 strlen("Organization: "), FALSE);
6158 g_string_append_printf(header, "Organization: %s\n", buf);
6161 /* Program version and system info */
6162 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6163 !compose->newsgroup_list) {
6164 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6166 gtk_major_version, gtk_minor_version, gtk_micro_version,
6169 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6170 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6172 gtk_major_version, gtk_minor_version, gtk_micro_version,
6176 /* custom headers */
6177 if (compose->account->add_customhdr) {
6180 for (cur = compose->account->customhdr_list; cur != NULL;
6182 CustomHeader *chdr = (CustomHeader *)cur->data;
6184 if (custom_header_is_allowed(chdr->name)) {
6185 compose_convert_header
6186 (compose, buf, sizeof(buf),
6187 chdr->value ? chdr->value : "",
6188 strlen(chdr->name) + 2, FALSE);
6189 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6194 /* Automatic Faces and X-Faces */
6195 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6196 g_string_append_printf(header, "X-Face: %s\n", buf);
6198 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6199 g_string_append_printf(header, "X-Face: %s\n", buf);
6201 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6202 g_string_append_printf(header, "Face: %s\n", buf);
6204 else if (get_default_face (buf, sizeof(buf)) == 0) {
6205 g_string_append_printf(header, "Face: %s\n", buf);
6209 switch (compose->priority) {
6210 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6211 "X-Priority: 1 (Highest)\n");
6213 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6214 "X-Priority: 2 (High)\n");
6216 case PRIORITY_NORMAL: break;
6217 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6218 "X-Priority: 4 (Low)\n");
6220 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6221 "X-Priority: 5 (Lowest)\n");
6223 default: debug_print("compose: priority unknown : %d\n",
6227 /* Request Return Receipt */
6228 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6229 if (compose->return_receipt) {
6230 if (compose->account->name
6231 && *compose->account->name) {
6232 compose_convert_header(compose, buf, sizeof(buf),
6233 compose->account->name,
6234 strlen("Disposition-Notification-To: "),
6236 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6238 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6242 /* get special headers */
6243 for (list = compose->header_list; list; list = list->next) {
6244 ComposeHeaderEntry *headerentry;
6247 gchar *headername_wcolon;
6248 const gchar *headername_trans;
6251 gboolean standard_header = FALSE;
6253 headerentry = ((ComposeHeaderEntry *)list->data);
6255 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6257 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6262 if (!strstr(tmp, ":")) {
6263 headername_wcolon = g_strconcat(tmp, ":", NULL);
6264 headername = g_strdup(tmp);
6266 headername_wcolon = g_strdup(tmp);
6267 headername = g_strdup(strtok(tmp, ":"));
6271 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6272 Xstrdup_a(headervalue, entry_str, return NULL);
6273 subst_char(headervalue, '\r', ' ');
6274 subst_char(headervalue, '\n', ' ');
6275 string = std_headers;
6276 while (*string != NULL) {
6277 headername_trans = prefs_common_translated_header_name(*string);
6278 if (!strcmp(headername_trans, headername_wcolon))
6279 standard_header = TRUE;
6282 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6283 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6286 g_free(headername_wcolon);
6290 g_string_free(header, FALSE);
6295 #undef IS_IN_CUSTOM_HEADER
6297 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6298 gint header_len, gboolean addr_field)
6300 gchar *tmpstr = NULL;
6301 const gchar *out_codeset = NULL;
6303 cm_return_if_fail(src != NULL);
6304 cm_return_if_fail(dest != NULL);
6306 if (len < 1) return;
6308 tmpstr = g_strdup(src);
6310 subst_char(tmpstr, '\n', ' ');
6311 subst_char(tmpstr, '\r', ' ');
6314 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6315 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6316 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6321 codeconv_set_strict(TRUE);
6322 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6323 conv_get_charset_str(compose->out_encoding));
6324 codeconv_set_strict(FALSE);
6326 if (!dest || *dest == '\0') {
6327 gchar *test_conv_global_out = NULL;
6328 gchar *test_conv_reply = NULL;
6330 /* automatic mode. be automatic. */
6331 codeconv_set_strict(TRUE);
6333 out_codeset = conv_get_outgoing_charset_str();
6335 debug_print("trying to convert to %s\n", out_codeset);
6336 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6339 if (!test_conv_global_out && compose->orig_charset
6340 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6341 out_codeset = compose->orig_charset;
6342 debug_print("failure; trying to convert to %s\n", out_codeset);
6343 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6346 if (!test_conv_global_out && !test_conv_reply) {
6348 out_codeset = CS_INTERNAL;
6349 debug_print("finally using %s\n", out_codeset);
6351 g_free(test_conv_global_out);
6352 g_free(test_conv_reply);
6353 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6355 codeconv_set_strict(FALSE);
6360 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6364 cm_return_if_fail(user_data != NULL);
6366 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6367 g_strstrip(address);
6368 if (*address != '\0') {
6369 gchar *name = procheader_get_fromname(address);
6370 extract_address(address);
6371 addressbook_add_contact(name, address, NULL, NULL);
6376 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6378 GtkWidget *menuitem;
6381 cm_return_if_fail(menu != NULL);
6382 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6384 menuitem = gtk_separator_menu_item_new();
6385 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6386 gtk_widget_show(menuitem);
6388 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6389 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6391 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6392 g_strstrip(address);
6393 if (*address == '\0') {
6394 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6397 g_signal_connect(G_OBJECT(menuitem), "activate",
6398 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6399 gtk_widget_show(menuitem);
6402 static void compose_create_header_entry(Compose *compose)
6404 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6411 const gchar *header = NULL;
6412 ComposeHeaderEntry *headerentry;
6413 gboolean standard_header = FALSE;
6414 GtkListStore *model;
6416 #if !(GTK_CHECK_VERSION(2,12,0))
6417 GtkTooltips *tips = compose->tooltips;
6420 headerentry = g_new0(ComposeHeaderEntry, 1);
6423 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6424 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6425 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6427 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6429 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6431 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6432 COMPOSE_NEWSGROUPS);
6433 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6435 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6436 COMPOSE_FOLLOWUPTO);
6438 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6439 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6440 G_CALLBACK(compose_grab_focus_cb), compose);
6441 gtk_widget_show(combo);
6442 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6443 compose->header_nextrow, compose->header_nextrow+1,
6444 GTK_SHRINK, GTK_FILL, 0, 0);
6445 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6446 const gchar *last_header_entry = gtk_entry_get_text(
6447 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6449 while (*string != NULL) {
6450 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6451 standard_header = TRUE;
6454 if (standard_header)
6455 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6457 if (!compose->header_last || !standard_header) {
6458 switch(compose->account->protocol) {
6460 header = prefs_common_translated_header_name("Newsgroups:");
6463 header = prefs_common_translated_header_name("To:");
6468 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6470 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6471 G_CALLBACK(compose_grab_focus_cb), compose);
6473 /* Entry field with cleanup button */
6474 #if GTK_CHECK_VERSION(2, 8, 0)
6475 button = gtk_button_new();
6476 gtk_button_set_image(GTK_BUTTON(button),
6477 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6479 button = gtk_button_new_with_label(_("Clear"));
6481 gtk_widget_show(button);
6482 CLAWS_SET_TIP(button,
6483 _("Delete entry contents"));
6484 entry = gtk_entry_new();
6485 gtk_widget_show(entry);
6486 CLAWS_SET_TIP(entry,
6487 _("Use <tab> to autocomplete from addressbook"));
6488 hbox = gtk_hbox_new (FALSE, 0);
6489 gtk_widget_show(hbox);
6490 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6491 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6492 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6493 compose->header_nextrow, compose->header_nextrow+1,
6494 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6496 g_signal_connect(G_OBJECT(entry), "key-press-event",
6497 G_CALLBACK(compose_headerentry_key_press_event_cb),
6499 g_signal_connect(G_OBJECT(entry), "changed",
6500 G_CALLBACK(compose_headerentry_changed_cb),
6502 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6503 G_CALLBACK(compose_grab_focus_cb), compose);
6505 g_signal_connect(G_OBJECT(button), "clicked",
6506 G_CALLBACK(compose_headerentry_button_clicked_cb),
6510 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6511 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6512 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6513 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6514 G_CALLBACK(compose_header_drag_received_cb),
6516 g_signal_connect(G_OBJECT(entry), "drag-drop",
6517 G_CALLBACK(compose_drag_drop),
6519 g_signal_connect(G_OBJECT(entry), "populate-popup",
6520 G_CALLBACK(compose_entry_popup_extend),
6523 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6525 headerentry->compose = compose;
6526 headerentry->combo = combo;
6527 headerentry->entry = entry;
6528 headerentry->button = button;
6529 headerentry->hbox = hbox;
6530 headerentry->headernum = compose->header_nextrow;
6531 headerentry->type = PREF_NONE;
6533 compose->header_nextrow++;
6534 compose->header_last = headerentry;
6535 compose->header_list =
6536 g_slist_append(compose->header_list,
6540 static void compose_add_header_entry(Compose *compose, const gchar *header,
6541 gchar *text, ComposePrefType pref_type)
6543 ComposeHeaderEntry *last_header = compose->header_last;
6544 gchar *tmp = g_strdup(text), *email;
6545 gboolean replyto_hdr = g_str_has_suffix(header, "-To:");
6547 extract_address(tmp);
6548 email = g_utf8_strdown(tmp, -1);
6550 if (replyto_hdr == FALSE &&
6551 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6553 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6554 header, text, (gint) pref_type);
6560 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6561 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6562 last_header->type = pref_type;
6564 if (replyto_hdr == FALSE)
6565 g_hash_table_insert(compose->email_hashtable, email,
6566 GUINT_TO_POINTER(1));
6573 static void compose_destroy_headerentry(Compose *compose,
6574 ComposeHeaderEntry *headerentry)
6576 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6579 extract_address(text);
6580 email = g_utf8_strdown(text, -1);
6581 g_hash_table_remove(compose->email_hashtable, email);
6585 gtk_widget_destroy(headerentry->combo);
6586 gtk_widget_destroy(headerentry->entry);
6587 gtk_widget_destroy(headerentry->button);
6588 gtk_widget_destroy(headerentry->hbox);
6589 g_free(headerentry);
6592 static void compose_remove_header_entries(Compose *compose)
6595 for (list = compose->header_list; list; list = list->next)
6596 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6598 compose->header_last = NULL;
6599 g_slist_free(compose->header_list);
6600 compose->header_list = NULL;
6601 compose->header_nextrow = 1;
6602 compose_create_header_entry(compose);
6605 static GtkWidget *compose_create_header(Compose *compose)
6607 GtkWidget *from_optmenu_hbox;
6608 GtkWidget *header_scrolledwin;
6609 GtkWidget *header_table;
6613 /* header labels and entries */
6614 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6615 gtk_widget_show(header_scrolledwin);
6616 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6618 header_table = gtk_table_new(2, 2, FALSE);
6619 gtk_widget_show(header_table);
6620 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6621 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6622 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6625 /* option menu for selecting accounts */
6626 from_optmenu_hbox = compose_account_option_menu_create(compose);
6627 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6628 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6631 compose->header_table = header_table;
6632 compose->header_list = NULL;
6633 compose->header_nextrow = count;
6635 compose_create_header_entry(compose);
6637 compose->table = NULL;
6639 return header_scrolledwin ;
6642 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6644 Compose *compose = (Compose *)data;
6645 GdkEventButton event;
6648 event.time = gtk_get_current_event_time();
6650 return attach_button_pressed(compose->attach_clist, &event, compose);
6653 static GtkWidget *compose_create_attach(Compose *compose)
6655 GtkWidget *attach_scrwin;
6656 GtkWidget *attach_clist;
6658 GtkListStore *store;
6659 GtkCellRenderer *renderer;
6660 GtkTreeViewColumn *column;
6661 GtkTreeSelection *selection;
6663 /* attachment list */
6664 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6665 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6666 GTK_POLICY_AUTOMATIC,
6667 GTK_POLICY_AUTOMATIC);
6668 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6670 store = gtk_list_store_new(N_ATTACH_COLS,
6675 G_TYPE_AUTO_POINTER,
6677 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6678 (GTK_TREE_MODEL(store)));
6679 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6680 g_object_unref(store);
6682 renderer = gtk_cell_renderer_text_new();
6683 column = gtk_tree_view_column_new_with_attributes
6684 (_("Mime type"), renderer, "text",
6685 COL_MIMETYPE, NULL);
6686 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6688 renderer = gtk_cell_renderer_text_new();
6689 column = gtk_tree_view_column_new_with_attributes
6690 (_("Size"), renderer, "text",
6692 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6694 renderer = gtk_cell_renderer_text_new();
6695 column = gtk_tree_view_column_new_with_attributes
6696 (_("Name"), renderer, "text",
6698 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6700 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6701 prefs_common.use_stripes_everywhere);
6702 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6703 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6705 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6706 G_CALLBACK(attach_selected), compose);
6707 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6708 G_CALLBACK(attach_button_pressed), compose);
6710 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6711 G_CALLBACK(popup_attach_button_pressed), compose);
6713 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6714 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6715 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6716 G_CALLBACK(popup_attach_button_pressed), compose);
6718 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6719 G_CALLBACK(attach_key_pressed), compose);
6722 gtk_drag_dest_set(attach_clist,
6723 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6724 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6725 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6726 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6727 G_CALLBACK(compose_attach_drag_received_cb),
6729 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6730 G_CALLBACK(compose_drag_drop),
6733 compose->attach_scrwin = attach_scrwin;
6734 compose->attach_clist = attach_clist;
6736 return attach_scrwin;
6739 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6740 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6742 static GtkWidget *compose_create_others(Compose *compose)
6745 GtkWidget *savemsg_checkbtn;
6746 GtkWidget *savemsg_combo;
6747 GtkWidget *savemsg_select;
6750 gchar *folderidentifier;
6752 /* Table for settings */
6753 table = gtk_table_new(3, 1, FALSE);
6754 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6755 gtk_widget_show(table);
6756 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6759 /* Save Message to folder */
6760 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6761 gtk_widget_show(savemsg_checkbtn);
6762 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6763 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6764 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6766 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6767 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6769 savemsg_combo = gtk_combo_box_entry_new_text();
6770 compose->savemsg_checkbtn = savemsg_checkbtn;
6771 compose->savemsg_combo = savemsg_combo;
6772 gtk_widget_show(savemsg_combo);
6774 if (prefs_common.compose_save_to_history)
6775 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
6776 prefs_common.compose_save_to_history);
6778 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
6779 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
6780 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
6781 G_CALLBACK(compose_grab_focus_cb), compose);
6782 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6783 folderidentifier = folder_item_get_identifier(account_get_special_folder
6784 (compose->account, F_OUTBOX));
6785 compose_set_save_to(compose, folderidentifier);
6786 g_free(folderidentifier);
6789 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6790 gtk_widget_show(savemsg_select);
6791 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6792 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6793 G_CALLBACK(compose_savemsg_select_cb),
6801 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6803 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
6804 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6807 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6812 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6815 path = folder_item_get_identifier(dest);
6817 compose_set_save_to(compose, path);
6821 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6822 GdkAtom clip, GtkTextIter *insert_place);
6825 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6829 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6831 if (event->button == 3) {
6833 GtkTextIter sel_start, sel_end;
6834 gboolean stuff_selected;
6836 /* move the cursor to allow GtkAspell to check the word
6837 * under the mouse */
6838 if (event->x && event->y) {
6839 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6840 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6842 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6845 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6846 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6849 stuff_selected = gtk_text_buffer_get_selection_bounds(
6851 &sel_start, &sel_end);
6853 gtk_text_buffer_place_cursor (buffer, &iter);
6854 /* reselect stuff */
6856 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6857 gtk_text_buffer_select_range(buffer,
6858 &sel_start, &sel_end);
6860 return FALSE; /* pass the event so that the right-click goes through */
6863 if (event->button == 2) {
6868 /* get the middle-click position to paste at the correct place */
6869 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6870 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6872 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6875 entry_paste_clipboard(compose, text,
6876 prefs_common.linewrap_pastes,
6877 GDK_SELECTION_PRIMARY, &iter);
6885 static void compose_spell_menu_changed(void *data)
6887 Compose *compose = (Compose *)data;
6889 GtkWidget *menuitem;
6890 GtkWidget *parent_item;
6891 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6894 if (compose->gtkaspell == NULL)
6897 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
6898 "/Menu/Spelling/Options");
6900 /* setting the submenu removes /Spelling/Options from the factory
6901 * so we need to save it */
6903 if (parent_item == NULL) {
6904 parent_item = compose->aspell_options_menu;
6905 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
6907 compose->aspell_options_menu = parent_item;
6909 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6911 spell_menu = g_slist_reverse(spell_menu);
6912 for (items = spell_menu;
6913 items; items = items->next) {
6914 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6915 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6916 gtk_widget_show(GTK_WIDGET(menuitem));
6918 g_slist_free(spell_menu);
6920 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6921 gtk_widget_show(parent_item);
6924 static void compose_dict_changed(void *data)
6926 Compose *compose = (Compose *) data;
6928 if(compose->gtkaspell &&
6929 compose->gtkaspell->recheck_when_changing_dict == FALSE)
6932 gtkaspell_highlight_all(compose->gtkaspell);
6933 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
6937 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6939 Compose *compose = (Compose *)data;
6940 GdkEventButton event;
6943 event.time = gtk_get_current_event_time();
6947 return text_clicked(compose->text, &event, compose);
6950 static gboolean compose_force_window_origin = TRUE;
6951 static Compose *compose_create(PrefsAccount *account,
6960 GtkWidget *handlebox;
6962 GtkWidget *notebook;
6964 GtkWidget *attach_hbox;
6965 GtkWidget *attach_lab1;
6966 GtkWidget *attach_lab2;
6971 GtkWidget *subject_hbox;
6972 GtkWidget *subject_frame;
6973 GtkWidget *subject_entry;
6977 GtkWidget *edit_vbox;
6978 GtkWidget *ruler_hbox;
6980 GtkWidget *scrolledwin;
6982 GtkTextBuffer *buffer;
6983 GtkClipboard *clipboard;
6986 UndoMain *undostruct;
6988 gchar *titles[N_ATTACH_COLS];
6989 GtkWidget *popupmenu;
6990 GtkWidget *tmpl_menu;
6991 GtkActionGroup *action_group = NULL;
6994 GtkAspell * gtkaspell = NULL;
6997 static GdkGeometry geometry;
6999 cm_return_val_if_fail(account != NULL, NULL);
7001 debug_print("Creating compose window...\n");
7002 compose = g_new0(Compose, 1);
7004 titles[COL_MIMETYPE] = _("MIME type");
7005 titles[COL_SIZE] = _("Size");
7006 titles[COL_NAME] = _("Name");
7008 compose->batch = batch;
7009 compose->account = account;
7010 compose->folder = folder;
7012 compose->mutex = g_mutex_new();
7013 compose->set_cursor_pos = -1;
7015 #if !(GTK_CHECK_VERSION(2,12,0))
7016 compose->tooltips = tips;
7019 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7021 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7022 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
7024 if (!geometry.max_width) {
7025 geometry.max_width = gdk_screen_width();
7026 geometry.max_height = gdk_screen_height();
7029 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7030 &geometry, GDK_HINT_MAX_SIZE);
7031 if (!geometry.min_width) {
7032 geometry.min_width = 600;
7033 geometry.min_height = 440;
7035 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7036 &geometry, GDK_HINT_MIN_SIZE);
7038 #ifndef GENERIC_UMPC
7039 if (compose_force_window_origin)
7040 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7041 prefs_common.compose_y);
7043 g_signal_connect(G_OBJECT(window), "delete_event",
7044 G_CALLBACK(compose_delete_cb), compose);
7045 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7046 gtk_widget_realize(window);
7048 gtkut_widget_set_composer_icon(window);
7050 vbox = gtk_vbox_new(FALSE, 0);
7051 gtk_container_add(GTK_CONTAINER(window), vbox);
7053 compose->ui_manager = gtk_ui_manager_new();
7054 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7055 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7056 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7057 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7058 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7059 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7060 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7061 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7062 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7063 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7066 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7068 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7071 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7072 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7074 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7076 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7077 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7078 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7081 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7082 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7083 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7084 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7085 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7086 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7087 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7088 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7089 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7090 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7093 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7094 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7095 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7097 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7098 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7099 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7101 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7102 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7103 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7104 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7106 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7108 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7109 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7110 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7111 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7112 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7113 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7114 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7115 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7116 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7117 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7118 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7119 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7120 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7121 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7122 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7124 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7126 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7127 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7128 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7129 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7130 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7132 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7134 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7138 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7139 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7140 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7141 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7142 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7143 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7147 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7148 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7149 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7150 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7151 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7153 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7154 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7155 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7156 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7157 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7160 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7161 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7162 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7163 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7164 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7165 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7166 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7168 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7169 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7170 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7171 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7172 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7174 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7176 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7177 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7178 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7179 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7180 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7182 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7183 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)
7184 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)
7185 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7187 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7189 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7190 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)
7191 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)
7193 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7195 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7196 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)
7197 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7199 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7200 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)
7201 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7203 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7205 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7206 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)
7207 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7208 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7209 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7211 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7212 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)
7213 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)
7214 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7215 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7217 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7218 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7219 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7220 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7221 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7223 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7224 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7225 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)
7227 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7228 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7229 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7233 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7234 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7235 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7236 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7237 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7238 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7241 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7243 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7244 gtk_widget_show_all(menubar);
7246 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7248 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7250 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7253 if (prefs_common.toolbar_detachable) {
7254 handlebox = gtk_handle_box_new();
7256 handlebox = gtk_hbox_new(FALSE, 0);
7258 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7260 gtk_widget_realize(handlebox);
7262 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7265 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7269 vbox2 = gtk_vbox_new(FALSE, 2);
7270 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7271 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7274 notebook = gtk_notebook_new();
7275 gtk_widget_set_size_request(notebook, -1, 130);
7276 gtk_widget_show(notebook);
7278 /* header labels and entries */
7279 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7280 compose_create_header(compose),
7281 gtk_label_new_with_mnemonic(_("Hea_der")));
7282 /* attachment list */
7283 attach_hbox = gtk_hbox_new(FALSE, 0);
7284 gtk_widget_show(attach_hbox);
7286 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7287 gtk_widget_show(attach_lab1);
7288 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7290 attach_lab2 = gtk_label_new("");
7291 gtk_widget_show(attach_lab2);
7292 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7294 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7295 compose_create_attach(compose),
7298 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7299 compose_create_others(compose),
7300 gtk_label_new_with_mnemonic(_("Othe_rs")));
7303 subject_hbox = gtk_hbox_new(FALSE, 0);
7304 gtk_widget_show(subject_hbox);
7306 subject_frame = gtk_frame_new(NULL);
7307 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7308 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7309 gtk_widget_show(subject_frame);
7311 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7312 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7313 gtk_widget_show(subject);
7315 label = gtk_label_new(_("Subject:"));
7316 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7317 gtk_widget_show(label);
7320 subject_entry = claws_spell_entry_new();
7322 subject_entry = gtk_entry_new();
7324 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7325 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7326 G_CALLBACK(compose_grab_focus_cb), compose);
7327 gtk_widget_show(subject_entry);
7328 compose->subject_entry = subject_entry;
7329 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7331 edit_vbox = gtk_vbox_new(FALSE, 0);
7333 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7336 ruler_hbox = gtk_hbox_new(FALSE, 0);
7337 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7339 ruler = gtk_shruler_new();
7340 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
7341 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7345 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7346 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7347 GTK_POLICY_AUTOMATIC,
7348 GTK_POLICY_AUTOMATIC);
7349 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7351 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7352 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
7354 text = gtk_text_view_new();
7355 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7356 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7357 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7358 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7359 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7361 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7363 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7364 G_CALLBACK(compose_edit_size_alloc),
7366 g_signal_connect(G_OBJECT(buffer), "changed",
7367 G_CALLBACK(compose_changed_cb), compose);
7368 g_signal_connect(G_OBJECT(text), "grab_focus",
7369 G_CALLBACK(compose_grab_focus_cb), compose);
7370 g_signal_connect(G_OBJECT(buffer), "insert_text",
7371 G_CALLBACK(text_inserted), compose);
7372 g_signal_connect(G_OBJECT(text), "button_press_event",
7373 G_CALLBACK(text_clicked), compose);
7375 g_signal_connect(G_OBJECT(text), "popup-menu",
7376 G_CALLBACK(compose_popup_menu), compose);
7378 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7379 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7380 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7381 G_CALLBACK(compose_popup_menu), compose);
7383 g_signal_connect(G_OBJECT(subject_entry), "changed",
7384 G_CALLBACK(compose_changed_cb), compose);
7387 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7388 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7389 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7390 g_signal_connect(G_OBJECT(text), "drag_data_received",
7391 G_CALLBACK(compose_insert_drag_received_cb),
7393 g_signal_connect(G_OBJECT(text), "drag-drop",
7394 G_CALLBACK(compose_drag_drop),
7396 gtk_widget_show_all(vbox);
7398 /* pane between attach clist and text */
7399 paned = gtk_vpaned_new();
7400 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7402 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7403 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7405 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7407 gtk_paned_add1(GTK_PANED(paned), notebook);
7408 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7409 gtk_widget_show_all(paned);
7412 if (prefs_common.textfont) {
7413 PangoFontDescription *font_desc;
7415 font_desc = pango_font_description_from_string
7416 (prefs_common.textfont);
7418 gtk_widget_modify_font(text, font_desc);
7419 pango_font_description_free(font_desc);
7423 gtk_action_group_add_actions(action_group, compose_popup_entries,
7424 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7430 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7432 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7434 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7435 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7436 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7438 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7440 undostruct = undo_init(text);
7441 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7444 address_completion_start(window);
7446 compose->window = window;
7447 compose->vbox = vbox;
7448 compose->menubar = menubar;
7449 compose->handlebox = handlebox;
7451 compose->vbox2 = vbox2;
7453 compose->paned = paned;
7455 compose->attach_label = attach_lab2;
7457 compose->notebook = notebook;
7458 compose->edit_vbox = edit_vbox;
7459 compose->ruler_hbox = ruler_hbox;
7460 compose->ruler = ruler;
7461 compose->scrolledwin = scrolledwin;
7462 compose->text = text;
7464 compose->focused_editable = NULL;
7466 compose->popupmenu = popupmenu;
7468 compose->tmpl_menu = tmpl_menu;
7470 compose->mode = mode;
7471 compose->rmode = mode;
7473 compose->targetinfo = NULL;
7474 compose->replyinfo = NULL;
7475 compose->fwdinfo = NULL;
7477 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7478 g_str_equal, (GDestroyNotify) g_free, NULL);
7480 compose->replyto = NULL;
7482 compose->bcc = NULL;
7483 compose->followup_to = NULL;
7485 compose->ml_post = NULL;
7487 compose->inreplyto = NULL;
7488 compose->references = NULL;
7489 compose->msgid = NULL;
7490 compose->boundary = NULL;
7492 compose->autowrap = prefs_common.autowrap;
7493 compose->autoindent = prefs_common.auto_indent;
7494 compose->use_signing = FALSE;
7495 compose->use_encryption = FALSE;
7496 compose->privacy_system = NULL;
7498 compose->modified = FALSE;
7500 compose->return_receipt = FALSE;
7502 compose->to_list = NULL;
7503 compose->newsgroup_list = NULL;
7505 compose->undostruct = undostruct;
7507 compose->sig_str = NULL;
7509 compose->exteditor_file = NULL;
7510 compose->exteditor_pid = -1;
7511 compose->exteditor_tag = -1;
7512 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7515 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7516 if (mode != COMPOSE_REDIRECT) {
7517 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7518 strcmp(prefs_common.dictionary, "")) {
7519 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7520 prefs_common.alt_dictionary,
7521 conv_get_locale_charset_str(),
7522 prefs_common.misspelled_col,
7523 prefs_common.check_while_typing,
7524 prefs_common.recheck_when_changing_dict,
7525 prefs_common.use_alternate,
7526 prefs_common.use_both_dicts,
7527 GTK_TEXT_VIEW(text),
7528 GTK_WINDOW(compose->window),
7529 compose_dict_changed,
7530 compose_spell_menu_changed,
7533 alertpanel_error(_("Spell checker could not "
7535 gtkaspell_checkers_strerror());
7536 gtkaspell_checkers_reset_error();
7538 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7542 compose->gtkaspell = gtkaspell;
7543 compose_spell_menu_changed(compose);
7544 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7547 compose_select_account(compose, account, TRUE);
7549 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7550 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7552 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7553 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7555 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7556 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7558 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7559 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7561 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7562 if (account->protocol != A_NNTP)
7563 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7564 prefs_common_translated_header_name("To:"));
7566 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7567 prefs_common_translated_header_name("Newsgroups:"));
7569 addressbook_set_target_compose(compose);
7571 if (mode != COMPOSE_REDIRECT)
7572 compose_set_template_menu(compose);
7574 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7577 compose_list = g_list_append(compose_list, compose);
7579 if (!prefs_common.show_ruler)
7580 gtk_widget_hide(ruler_hbox);
7582 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7585 compose->priority = PRIORITY_NORMAL;
7586 compose_update_priority_menu_item(compose);
7588 compose_set_out_encoding(compose);
7591 compose_update_actions_menu(compose);
7593 /* Privacy Systems menu */
7594 compose_update_privacy_systems_menu(compose);
7596 activate_privacy_system(compose, account, TRUE);
7597 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7599 gtk_widget_realize(window);
7601 gtk_widget_show(window);
7603 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7604 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7611 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7616 GtkWidget *optmenubox;
7619 GtkWidget *from_name = NULL;
7620 #if !(GTK_CHECK_VERSION(2,12,0))
7621 GtkTooltips *tips = compose->tooltips;
7624 gint num = 0, def_menu = 0;
7626 accounts = account_get_list();
7627 cm_return_val_if_fail(accounts != NULL, NULL);
7629 optmenubox = gtk_event_box_new();
7630 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7631 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7633 hbox = gtk_hbox_new(FALSE, 6);
7634 from_name = gtk_entry_new();
7636 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7637 G_CALLBACK(compose_grab_focus_cb), compose);
7639 for (; accounts != NULL; accounts = accounts->next, num++) {
7640 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7641 gchar *name, *from = NULL;
7643 if (ac == compose->account) def_menu = num;
7645 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7648 if (ac == compose->account) {
7649 if (ac->name && *ac->name) {
7651 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7652 from = g_strdup_printf("%s <%s>",
7654 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7656 from = g_strdup_printf("%s",
7658 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7661 COMBOBOX_ADD(menu, name, ac->account_id);
7666 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7668 g_signal_connect(G_OBJECT(optmenu), "changed",
7669 G_CALLBACK(account_activated),
7671 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7672 G_CALLBACK(compose_entry_popup_extend),
7675 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7676 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7678 CLAWS_SET_TIP(optmenubox,
7679 _("Account to use for this email"));
7680 CLAWS_SET_TIP(from_name,
7681 _("Sender address to be used"));
7683 compose->from_name = from_name;
7688 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7690 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7691 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7692 Compose *compose = (Compose *) data;
7694 compose->priority = value;
7698 static void compose_reply_change_mode(Compose *compose,
7701 gboolean was_modified = compose->modified;
7703 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7705 cm_return_if_fail(compose->replyinfo != NULL);
7707 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7709 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7711 if (action == COMPOSE_REPLY_TO_ALL)
7713 if (action == COMPOSE_REPLY_TO_SENDER)
7715 if (action == COMPOSE_REPLY_TO_LIST)
7718 compose_remove_header_entries(compose);
7719 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7720 if (compose->account->set_autocc && compose->account->auto_cc)
7721 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7723 if (compose->account->set_autobcc && compose->account->auto_bcc)
7724 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7726 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7727 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7728 compose_show_first_last_header(compose, TRUE);
7729 compose->modified = was_modified;
7730 compose_set_title(compose);
7733 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7735 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7736 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7737 Compose *compose = (Compose *) data;
7740 compose_reply_change_mode(compose, value);
7743 static void compose_update_priority_menu_item(Compose * compose)
7745 GtkWidget *menuitem = NULL;
7746 switch (compose->priority) {
7747 case PRIORITY_HIGHEST:
7748 menuitem = gtk_ui_manager_get_widget
7749 (compose->ui_manager, "/Menu/Options/Priority/Highest");
7752 menuitem = gtk_ui_manager_get_widget
7753 (compose->ui_manager, "/Menu/Options/Priority/High");
7755 case PRIORITY_NORMAL:
7756 menuitem = gtk_ui_manager_get_widget
7757 (compose->ui_manager, "/Menu/Options/Priority/Normal");
7760 menuitem = gtk_ui_manager_get_widget
7761 (compose->ui_manager, "/Menu/Options/Priority/Low");
7763 case PRIORITY_LOWEST:
7764 menuitem = gtk_ui_manager_get_widget
7765 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
7768 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7771 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7773 Compose *compose = (Compose *) data;
7775 gboolean can_sign = FALSE, can_encrypt = FALSE;
7777 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7779 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7782 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7783 g_free(compose->privacy_system);
7784 compose->privacy_system = NULL;
7785 if (systemid != NULL) {
7786 compose->privacy_system = g_strdup(systemid);
7788 can_sign = privacy_system_can_sign(systemid);
7789 can_encrypt = privacy_system_can_encrypt(systemid);
7792 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7794 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7795 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7798 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7800 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7801 GtkWidget *menuitem = NULL;
7803 gboolean can_sign = FALSE, can_encrypt = FALSE;
7804 gboolean found = FALSE;
7806 if (compose->privacy_system != NULL) {
7808 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
7809 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
7810 cm_return_if_fail(menuitem != NULL);
7812 amenu = GTK_MENU_SHELL(menuitem)->children;
7814 while (amenu != NULL) {
7815 GList *alist = amenu->next;
7817 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7818 if (systemid != NULL) {
7819 if (strcmp(systemid, compose->privacy_system) == 0 &&
7820 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7821 menuitem = GTK_WIDGET(amenu->data);
7823 can_sign = privacy_system_can_sign(systemid);
7824 can_encrypt = privacy_system_can_encrypt(systemid);
7828 } else if (strlen(compose->privacy_system) == 0 &&
7829 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
7830 menuitem = GTK_WIDGET(amenu->data);
7833 can_encrypt = FALSE;
7840 if (menuitem != NULL)
7841 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7843 if (warn && !found && strlen(compose->privacy_system)) {
7844 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7845 "will not be able to sign or encrypt this message."),
7846 compose->privacy_system);
7850 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
7851 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
7854 static void compose_set_out_encoding(Compose *compose)
7856 CharSet out_encoding;
7857 const gchar *branch = NULL;
7858 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7860 switch(out_encoding) {
7861 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7862 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
7863 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
7864 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
7865 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
7866 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
7867 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
7868 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
7869 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
7870 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
7871 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
7872 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
7873 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
7874 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
7875 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
7876 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
7877 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
7878 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
7879 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
7880 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
7881 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
7882 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
7883 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
7884 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
7885 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
7886 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
7887 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
7888 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
7889 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
7890 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
7891 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
7892 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
7894 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
7897 static void compose_set_template_menu(Compose *compose)
7899 GSList *tmpl_list, *cur;
7903 tmpl_list = template_get_config();
7905 menu = gtk_menu_new();
7907 gtk_menu_set_accel_group (GTK_MENU (menu),
7908 gtk_ui_manager_get_accel_group(compose->ui_manager));
7909 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7910 Template *tmpl = (Template *)cur->data;
7911 gchar *accel_path = NULL;
7912 item = gtk_menu_item_new_with_label(tmpl->name);
7913 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7914 g_signal_connect(G_OBJECT(item), "activate",
7915 G_CALLBACK(compose_template_activate_cb),
7917 g_object_set_data(G_OBJECT(item), "template", tmpl);
7918 gtk_widget_show(item);
7919 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
7920 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
7924 gtk_widget_show(menu);
7925 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7928 void compose_update_actions_menu(Compose *compose)
7930 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
7933 static void compose_update_privacy_systems_menu(Compose *compose)
7935 static gchar *branch_path = "/Menu/Options/PrivacySystem";
7936 GSList *systems, *cur;
7938 GtkWidget *system_none;
7940 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
7941 GtkWidget *privacy_menu = gtk_menu_new();
7943 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
7944 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
7946 g_signal_connect(G_OBJECT(system_none), "activate",
7947 G_CALLBACK(compose_set_privacy_system_cb), compose);
7949 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
7950 gtk_widget_show(system_none);
7952 systems = privacy_get_system_ids();
7953 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7954 gchar *systemid = cur->data;
7956 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7957 widget = gtk_radio_menu_item_new_with_label(group,
7958 privacy_system_get_name(systemid));
7959 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7960 g_strdup(systemid), g_free);
7961 g_signal_connect(G_OBJECT(widget), "activate",
7962 G_CALLBACK(compose_set_privacy_system_cb), compose);
7964 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
7965 gtk_widget_show(widget);
7968 g_slist_free(systems);
7969 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
7970 gtk_widget_show_all(privacy_menu);
7971 gtk_widget_show_all(privacy_menuitem);
7974 void compose_reflect_prefs_all(void)
7979 for (cur = compose_list; cur != NULL; cur = cur->next) {
7980 compose = (Compose *)cur->data;
7981 compose_set_template_menu(compose);
7985 void compose_reflect_prefs_pixmap_theme(void)
7990 for (cur = compose_list; cur != NULL; cur = cur->next) {
7991 compose = (Compose *)cur->data;
7992 toolbar_update(TOOLBAR_COMPOSE, compose);
7996 static const gchar *compose_quote_char_from_context(Compose *compose)
7998 const gchar *qmark = NULL;
8000 cm_return_val_if_fail(compose != NULL, NULL);
8002 switch (compose->mode) {
8003 /* use forward-specific quote char */
8004 case COMPOSE_FORWARD:
8005 case COMPOSE_FORWARD_AS_ATTACH:
8006 case COMPOSE_FORWARD_INLINE:
8007 if (compose->folder && compose->folder->prefs &&
8008 compose->folder->prefs->forward_with_format)
8009 qmark = compose->folder->prefs->forward_quotemark;
8010 else if (compose->account->forward_with_format)
8011 qmark = compose->account->forward_quotemark;
8013 qmark = prefs_common.fw_quotemark;
8016 /* use reply-specific quote char in all other modes */
8018 if (compose->folder && compose->folder->prefs &&
8019 compose->folder->prefs->reply_with_format)
8020 qmark = compose->folder->prefs->reply_quotemark;
8021 else if (compose->account->reply_with_format)
8022 qmark = compose->account->reply_quotemark;
8024 qmark = prefs_common.quotemark;
8028 if (qmark == NULL || *qmark == '\0')
8034 static void compose_template_apply(Compose *compose, Template *tmpl,
8038 GtkTextBuffer *buffer;
8042 gchar *parsed_str = NULL;
8043 gint cursor_pos = 0;
8044 const gchar *err_msg = _("The body of the template has an error at line %d.");
8047 /* process the body */
8049 text = GTK_TEXT_VIEW(compose->text);
8050 buffer = gtk_text_view_get_buffer(text);
8053 qmark = compose_quote_char_from_context(compose);
8055 if (compose->replyinfo != NULL) {
8058 gtk_text_buffer_set_text(buffer, "", -1);
8059 mark = gtk_text_buffer_get_insert(buffer);
8060 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8062 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8063 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8065 } else if (compose->fwdinfo != NULL) {
8068 gtk_text_buffer_set_text(buffer, "", -1);
8069 mark = gtk_text_buffer_get_insert(buffer);
8070 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8072 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8073 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8076 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8078 GtkTextIter start, end;
8081 gtk_text_buffer_get_start_iter(buffer, &start);
8082 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8083 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8085 /* clear the buffer now */
8087 gtk_text_buffer_set_text(buffer, "", -1);
8089 parsed_str = compose_quote_fmt(compose, dummyinfo,
8090 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8091 procmsg_msginfo_free( dummyinfo );
8097 gtk_text_buffer_set_text(buffer, "", -1);
8098 mark = gtk_text_buffer_get_insert(buffer);
8099 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8102 if (replace && parsed_str && compose->account->auto_sig)
8103 compose_insert_sig(compose, FALSE);
8105 if (replace && parsed_str) {
8106 gtk_text_buffer_get_start_iter(buffer, &iter);
8107 gtk_text_buffer_place_cursor(buffer, &iter);
8111 cursor_pos = quote_fmt_get_cursor_pos();
8112 compose->set_cursor_pos = cursor_pos;
8113 if (cursor_pos == -1)
8115 gtk_text_buffer_get_start_iter(buffer, &iter);
8116 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8117 gtk_text_buffer_place_cursor(buffer, &iter);
8120 /* process the other fields */
8122 compose_template_apply_fields(compose, tmpl);
8123 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8124 quote_fmt_reset_vartable();
8125 compose_changed_cb(NULL, compose);
8128 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8129 gtkaspell_highlight_all(compose->gtkaspell);
8133 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8135 MsgInfo* dummyinfo = NULL;
8136 MsgInfo *msginfo = NULL;
8139 if (compose->replyinfo != NULL)
8140 msginfo = compose->replyinfo;
8141 else if (compose->fwdinfo != NULL)
8142 msginfo = compose->fwdinfo;
8144 dummyinfo = compose_msginfo_new_from_compose(compose);
8145 msginfo = dummyinfo;
8148 if (tmpl->from && *tmpl->from != '\0') {
8150 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8151 compose->gtkaspell);
8153 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8155 quote_fmt_scan_string(tmpl->from);
8158 buf = quote_fmt_get_buffer();
8160 alertpanel_error(_("Template From format error."));
8162 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8166 if (tmpl->to && *tmpl->to != '\0') {
8168 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8169 compose->gtkaspell);
8171 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8173 quote_fmt_scan_string(tmpl->to);
8176 buf = quote_fmt_get_buffer();
8178 alertpanel_error(_("Template To format error."));
8180 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8184 if (tmpl->cc && *tmpl->cc != '\0') {
8186 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8187 compose->gtkaspell);
8189 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8191 quote_fmt_scan_string(tmpl->cc);
8194 buf = quote_fmt_get_buffer();
8196 alertpanel_error(_("Template Cc format error."));
8198 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8202 if (tmpl->bcc && *tmpl->bcc != '\0') {
8204 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8205 compose->gtkaspell);
8207 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8209 quote_fmt_scan_string(tmpl->bcc);
8212 buf = quote_fmt_get_buffer();
8214 alertpanel_error(_("Template Bcc format error."));
8216 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8220 /* process the subject */
8221 if (tmpl->subject && *tmpl->subject != '\0') {
8223 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8224 compose->gtkaspell);
8226 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8228 quote_fmt_scan_string(tmpl->subject);
8231 buf = quote_fmt_get_buffer();
8233 alertpanel_error(_("Template subject format error."));
8235 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8239 procmsg_msginfo_free( dummyinfo );
8242 static void compose_destroy(Compose *compose)
8244 GtkTextBuffer *buffer;
8245 GtkClipboard *clipboard;
8247 compose_list = g_list_remove(compose_list, compose);
8249 if (compose->updating) {
8250 debug_print("danger, not destroying anything now\n");
8251 compose->deferred_destroy = TRUE;
8254 /* NOTE: address_completion_end() does nothing with the window
8255 * however this may change. */
8256 address_completion_end(compose->window);
8258 slist_free_strings(compose->to_list);
8259 g_slist_free(compose->to_list);
8260 slist_free_strings(compose->newsgroup_list);
8261 g_slist_free(compose->newsgroup_list);
8262 slist_free_strings(compose->header_list);
8263 g_slist_free(compose->header_list);
8265 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8267 g_hash_table_destroy(compose->email_hashtable);
8269 procmsg_msginfo_free(compose->targetinfo);
8270 procmsg_msginfo_free(compose->replyinfo);
8271 procmsg_msginfo_free(compose->fwdinfo);
8273 g_free(compose->replyto);
8274 g_free(compose->cc);
8275 g_free(compose->bcc);
8276 g_free(compose->newsgroups);
8277 g_free(compose->followup_to);
8279 g_free(compose->ml_post);
8281 g_free(compose->inreplyto);
8282 g_free(compose->references);
8283 g_free(compose->msgid);
8284 g_free(compose->boundary);
8286 g_free(compose->redirect_filename);
8287 if (compose->undostruct)
8288 undo_destroy(compose->undostruct);
8290 g_free(compose->sig_str);
8292 g_free(compose->exteditor_file);
8294 g_free(compose->orig_charset);
8296 g_free(compose->privacy_system);
8298 if (addressbook_get_target_compose() == compose)
8299 addressbook_set_target_compose(NULL);
8302 if (compose->gtkaspell) {
8303 gtkaspell_delete(compose->gtkaspell);
8304 compose->gtkaspell = NULL;
8308 if (!compose->batch) {
8309 prefs_common.compose_width = compose->scrolledwin->allocation.width;
8310 prefs_common.compose_height = compose->window->allocation.height;
8313 if (!gtk_widget_get_parent(compose->paned))
8314 gtk_widget_destroy(compose->paned);
8315 gtk_widget_destroy(compose->popupmenu);
8317 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8318 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8319 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8321 gtk_widget_destroy(compose->window);
8322 toolbar_destroy(compose->toolbar);
8323 g_free(compose->toolbar);
8324 g_mutex_free(compose->mutex);
8328 static void compose_attach_info_free(AttachInfo *ainfo)
8330 g_free(ainfo->file);
8331 g_free(ainfo->content_type);
8332 g_free(ainfo->name);
8336 static void compose_attach_update_label(Compose *compose)
8341 GtkTreeModel *model;
8346 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8347 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8348 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8352 while(gtk_tree_model_iter_next(model, &iter))
8355 text = g_strdup_printf("(%d)", i);
8356 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8360 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8362 Compose *compose = (Compose *)data;
8363 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8364 GtkTreeSelection *selection;
8366 GtkTreeModel *model;
8368 selection = gtk_tree_view_get_selection(tree_view);
8369 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8374 for (cur = sel; cur != NULL; cur = cur->next) {
8375 GtkTreePath *path = cur->data;
8376 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8379 gtk_tree_path_free(path);
8382 for (cur = sel; cur != NULL; cur = cur->next) {
8383 GtkTreeRowReference *ref = cur->data;
8384 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8387 if (gtk_tree_model_get_iter(model, &iter, path))
8388 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8390 gtk_tree_path_free(path);
8391 gtk_tree_row_reference_free(ref);
8395 compose_attach_update_label(compose);
8398 static struct _AttachProperty
8401 GtkWidget *mimetype_entry;
8402 GtkWidget *encoding_optmenu;
8403 GtkWidget *path_entry;
8404 GtkWidget *filename_entry;
8406 GtkWidget *cancel_btn;
8409 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8411 gtk_tree_path_free((GtkTreePath *)ptr);
8414 static void compose_attach_property(GtkAction *action, gpointer data)
8416 Compose *compose = (Compose *)data;
8417 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8419 GtkComboBox *optmenu;
8420 GtkTreeSelection *selection;
8422 GtkTreeModel *model;
8425 static gboolean cancelled;
8427 /* only if one selected */
8428 selection = gtk_tree_view_get_selection(tree_view);
8429 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8432 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8436 path = (GtkTreePath *) sel->data;
8437 gtk_tree_model_get_iter(model, &iter, path);
8438 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8441 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8447 if (!attach_prop.window)
8448 compose_attach_property_create(&cancelled);
8449 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8450 gtk_widget_grab_focus(attach_prop.ok_btn);
8451 gtk_widget_show(attach_prop.window);
8452 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8454 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8455 if (ainfo->encoding == ENC_UNKNOWN)
8456 combobox_select_by_data(optmenu, ENC_BASE64);
8458 combobox_select_by_data(optmenu, ainfo->encoding);
8460 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8461 ainfo->content_type ? ainfo->content_type : "");
8462 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8463 ainfo->file ? ainfo->file : "");
8464 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8465 ainfo->name ? ainfo->name : "");
8468 const gchar *entry_text;
8470 gchar *cnttype = NULL;
8477 gtk_widget_hide(attach_prop.window);
8478 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8483 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8484 if (*entry_text != '\0') {
8487 text = g_strstrip(g_strdup(entry_text));
8488 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8489 cnttype = g_strdup(text);
8492 alertpanel_error(_("Invalid MIME type."));
8498 ainfo->encoding = combobox_get_active_data(optmenu);
8500 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8501 if (*entry_text != '\0') {
8502 if (is_file_exist(entry_text) &&
8503 (size = get_file_size(entry_text)) > 0)
8504 file = g_strdup(entry_text);
8507 (_("File doesn't exist or is empty."));
8513 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8514 if (*entry_text != '\0') {
8515 g_free(ainfo->name);
8516 ainfo->name = g_strdup(entry_text);
8520 g_free(ainfo->content_type);
8521 ainfo->content_type = cnttype;
8524 g_free(ainfo->file);
8528 ainfo->size = (goffset)size;
8530 /* update tree store */
8531 text = to_human_readable(ainfo->size);
8532 gtk_tree_model_get_iter(model, &iter, path);
8533 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8534 COL_MIMETYPE, ainfo->content_type,
8536 COL_NAME, ainfo->name,
8542 gtk_tree_path_free(path);
8545 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8547 label = gtk_label_new(str); \
8548 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8549 GTK_FILL, 0, 0, 0); \
8550 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8552 entry = gtk_entry_new(); \
8553 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8554 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8557 static void compose_attach_property_create(gboolean *cancelled)
8563 GtkWidget *mimetype_entry;
8566 GtkListStore *optmenu_menu;
8567 GtkWidget *path_entry;
8568 GtkWidget *filename_entry;
8571 GtkWidget *cancel_btn;
8572 GList *mime_type_list, *strlist;
8575 debug_print("Creating attach_property window...\n");
8577 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8578 gtk_widget_set_size_request(window, 480, -1);
8579 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8580 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8581 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8582 g_signal_connect(G_OBJECT(window), "delete_event",
8583 G_CALLBACK(attach_property_delete_event),
8585 g_signal_connect(G_OBJECT(window), "key_press_event",
8586 G_CALLBACK(attach_property_key_pressed),
8589 vbox = gtk_vbox_new(FALSE, 8);
8590 gtk_container_add(GTK_CONTAINER(window), vbox);
8592 table = gtk_table_new(4, 2, FALSE);
8593 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8594 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8595 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8597 label = gtk_label_new(_("MIME type"));
8598 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8600 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8601 mimetype_entry = gtk_combo_box_entry_new_text();
8602 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8603 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8605 /* stuff with list */
8606 mime_type_list = procmime_get_mime_type_list();
8608 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8609 MimeType *type = (MimeType *) mime_type_list->data;
8612 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8614 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8617 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8618 (GCompareFunc)strcmp2);
8621 for (mime_type_list = strlist; mime_type_list != NULL;
8622 mime_type_list = mime_type_list->next) {
8623 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8624 g_free(mime_type_list->data);
8626 g_list_free(strlist);
8627 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8628 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8630 label = gtk_label_new(_("Encoding"));
8631 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8633 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8635 hbox = gtk_hbox_new(FALSE, 0);
8636 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8637 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8639 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8640 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8642 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8643 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8644 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8645 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8646 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8648 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8650 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8651 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8653 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8654 &ok_btn, GTK_STOCK_OK,
8656 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8657 gtk_widget_grab_default(ok_btn);
8659 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8660 G_CALLBACK(attach_property_ok),
8662 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8663 G_CALLBACK(attach_property_cancel),
8666 gtk_widget_show_all(vbox);
8668 attach_prop.window = window;
8669 attach_prop.mimetype_entry = mimetype_entry;
8670 attach_prop.encoding_optmenu = optmenu;
8671 attach_prop.path_entry = path_entry;
8672 attach_prop.filename_entry = filename_entry;
8673 attach_prop.ok_btn = ok_btn;
8674 attach_prop.cancel_btn = cancel_btn;
8677 #undef SET_LABEL_AND_ENTRY
8679 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8685 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8691 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8692 gboolean *cancelled)
8700 static gboolean attach_property_key_pressed(GtkWidget *widget,
8702 gboolean *cancelled)
8704 if (event && event->keyval == GDK_Escape) {
8708 if (event && event->keyval == GDK_Return) {
8716 static void compose_exec_ext_editor(Compose *compose)
8723 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8724 G_DIR_SEPARATOR, compose);
8726 if (pipe(pipe_fds) < 0) {
8732 if ((pid = fork()) < 0) {
8739 /* close the write side of the pipe */
8742 compose->exteditor_file = g_strdup(tmp);
8743 compose->exteditor_pid = pid;
8745 compose_set_ext_editor_sensitive(compose, FALSE);
8748 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8750 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
8752 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8756 } else { /* process-monitoring process */
8762 /* close the read side of the pipe */
8765 if (compose_write_body_to_file(compose, tmp) < 0) {
8766 fd_write_all(pipe_fds[1], "2\n", 2);
8770 pid_ed = compose_exec_ext_editor_real(tmp);
8772 fd_write_all(pipe_fds[1], "1\n", 2);
8776 /* wait until editor is terminated */
8777 waitpid(pid_ed, NULL, 0);
8779 fd_write_all(pipe_fds[1], "0\n", 2);
8786 #endif /* G_OS_UNIX */
8790 static gint compose_exec_ext_editor_real(const gchar *file)
8797 cm_return_val_if_fail(file != NULL, -1);
8799 if ((pid = fork()) < 0) {
8804 if (pid != 0) return pid;
8806 /* grandchild process */
8808 if (setpgid(0, getppid()))
8811 if (prefs_common_get_ext_editor_cmd() &&
8812 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8813 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8814 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8816 if (prefs_common_get_ext_editor_cmd())
8817 g_warning("External editor command-line is invalid: '%s'\n",
8818 prefs_common_get_ext_editor_cmd());
8819 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8822 cmdline = strsplit_with_quote(buf, " ", 1024);
8823 execvp(cmdline[0], cmdline);
8826 g_strfreev(cmdline);
8831 static gboolean compose_ext_editor_kill(Compose *compose)
8833 pid_t pgid = compose->exteditor_pid * -1;
8836 ret = kill(pgid, 0);
8838 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8842 msg = g_strdup_printf
8843 (_("The external editor is still working.\n"
8844 "Force terminating the process?\n"
8845 "process group id: %d"), -pgid);
8846 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8847 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8851 if (val == G_ALERTALTERNATE) {
8852 g_source_remove(compose->exteditor_tag);
8853 g_io_channel_shutdown(compose->exteditor_ch,
8855 g_io_channel_unref(compose->exteditor_ch);
8857 if (kill(pgid, SIGTERM) < 0) perror("kill");
8858 waitpid(compose->exteditor_pid, NULL, 0);
8860 g_warning("Terminated process group id: %d", -pgid);
8861 g_warning("Temporary file: %s",
8862 compose->exteditor_file);
8864 compose_set_ext_editor_sensitive(compose, TRUE);
8866 g_free(compose->exteditor_file);
8867 compose->exteditor_file = NULL;
8868 compose->exteditor_pid = -1;
8869 compose->exteditor_ch = NULL;
8870 compose->exteditor_tag = -1;
8878 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8882 Compose *compose = (Compose *)data;
8885 debug_print(_("Compose: input from monitoring process\n"));
8887 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8889 g_io_channel_shutdown(source, FALSE, NULL);
8890 g_io_channel_unref(source);
8892 waitpid(compose->exteditor_pid, NULL, 0);
8894 if (buf[0] == '0') { /* success */
8895 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8896 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8898 gtk_text_buffer_set_text(buffer, "", -1);
8899 compose_insert_file(compose, compose->exteditor_file);
8900 compose_changed_cb(NULL, compose);
8901 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
8903 if (claws_unlink(compose->exteditor_file) < 0)
8904 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8905 } else if (buf[0] == '1') { /* failed */
8906 g_warning("Couldn't exec external editor\n");
8907 if (claws_unlink(compose->exteditor_file) < 0)
8908 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8909 } else if (buf[0] == '2') {
8910 g_warning("Couldn't write to file\n");
8911 } else if (buf[0] == '3') {
8912 g_warning("Pipe read failed\n");
8915 compose_set_ext_editor_sensitive(compose, TRUE);
8917 g_free(compose->exteditor_file);
8918 compose->exteditor_file = NULL;
8919 compose->exteditor_pid = -1;
8920 compose->exteditor_ch = NULL;
8921 compose->exteditor_tag = -1;
8926 static void compose_set_ext_editor_sensitive(Compose *compose,
8929 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
8930 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
8931 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
8932 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
8933 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
8934 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
8935 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
8937 gtk_widget_set_sensitive(compose->text, sensitive);
8938 if (compose->toolbar->send_btn)
8939 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8940 if (compose->toolbar->sendl_btn)
8941 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8942 if (compose->toolbar->draft_btn)
8943 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8944 if (compose->toolbar->insert_btn)
8945 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8946 if (compose->toolbar->sig_btn)
8947 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8948 if (compose->toolbar->exteditor_btn)
8949 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8950 if (compose->toolbar->linewrap_current_btn)
8951 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8952 if (compose->toolbar->linewrap_all_btn)
8953 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8955 #endif /* G_OS_UNIX */
8958 * compose_undo_state_changed:
8960 * Change the sensivity of the menuentries undo and redo
8962 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8963 gint redo_state, gpointer data)
8965 Compose *compose = (Compose *)data;
8967 switch (undo_state) {
8968 case UNDO_STATE_TRUE:
8969 if (!undostruct->undo_state) {
8970 undostruct->undo_state = TRUE;
8971 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
8974 case UNDO_STATE_FALSE:
8975 if (undostruct->undo_state) {
8976 undostruct->undo_state = FALSE;
8977 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
8980 case UNDO_STATE_UNCHANGED:
8982 case UNDO_STATE_REFRESH:
8983 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
8986 g_warning("Undo state not recognized");
8990 switch (redo_state) {
8991 case UNDO_STATE_TRUE:
8992 if (!undostruct->redo_state) {
8993 undostruct->redo_state = TRUE;
8994 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
8997 case UNDO_STATE_FALSE:
8998 if (undostruct->redo_state) {
8999 undostruct->redo_state = FALSE;
9000 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9003 case UNDO_STATE_UNCHANGED:
9005 case UNDO_STATE_REFRESH:
9006 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9009 g_warning("Redo state not recognized");
9014 /* callback functions */
9016 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9017 * includes "non-client" (windows-izm) in calculation, so this calculation
9018 * may not be accurate.
9020 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9021 GtkAllocation *allocation,
9022 GtkSHRuler *shruler)
9024 if (prefs_common.show_ruler) {
9025 gint char_width = 0, char_height = 0;
9026 gint line_width_in_chars;
9028 gtkut_get_font_size(GTK_WIDGET(widget),
9029 &char_width, &char_height);
9030 line_width_in_chars =
9031 (allocation->width - allocation->x) / char_width;
9033 /* got the maximum */
9034 gtk_ruler_set_range(GTK_RULER(shruler),
9035 0.0, line_width_in_chars, 0,
9036 /*line_width_in_chars*/ char_width);
9043 ComposeEntryType header;
9045 ComposePrefType type;
9046 gboolean entry_marked;
9049 static void account_activated(GtkComboBox *optmenu, gpointer data)
9051 Compose *compose = (Compose *)data;
9054 gchar *folderidentifier;
9055 gint account_id = 0;
9058 GSList *list, *saved_list = NULL;
9059 HeaderEntryState *state;
9060 GtkRcStyle *style = NULL;
9061 static GdkColor yellow;
9062 static gboolean color_set = FALSE;
9064 /* Get ID of active account in the combo box */
9065 menu = gtk_combo_box_get_model(optmenu);
9066 gtk_combo_box_get_active_iter(optmenu, &iter);
9067 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9069 ac = account_find_from_id(account_id);
9070 cm_return_if_fail(ac != NULL);
9072 if (ac != compose->account) {
9073 compose_select_account(compose, ac, FALSE);
9075 for (list = compose->header_list; list; list = list->next) {
9076 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9078 if (hentry->type == PREF_ACCOUNT || !list->next) {
9079 compose_destroy_headerentry(compose, hentry);
9083 state = g_malloc0(sizeof(HeaderEntryState));
9084 state->header = combobox_get_active_data(
9085 GTK_COMBO_BOX(hentry->combo));
9086 state->entry = gtk_editable_get_chars(
9087 GTK_EDITABLE(hentry->entry), 0, -1);
9088 state->type = hentry->type;
9091 gdk_color_parse("#f5f6be", &yellow);
9092 color_set = gdk_colormap_alloc_color(
9093 gdk_colormap_get_system(),
9094 &yellow, FALSE, TRUE);
9097 style = gtk_widget_get_modifier_style(hentry->entry);
9098 state->entry_marked = gdk_color_equal(&yellow,
9099 &style->base[GTK_STATE_NORMAL]);
9101 saved_list = g_slist_append(saved_list, state);
9102 compose_destroy_headerentry(compose, hentry);
9105 compose->header_last = NULL;
9106 g_slist_free(compose->header_list);
9107 compose->header_list = NULL;
9108 compose->header_nextrow = 1;
9109 compose_create_header_entry(compose);
9111 if (ac->set_autocc && ac->auto_cc)
9112 compose_entry_append(compose, ac->auto_cc,
9113 COMPOSE_CC, PREF_ACCOUNT);
9115 if (ac->set_autobcc && ac->auto_bcc)
9116 compose_entry_append(compose, ac->auto_bcc,
9117 COMPOSE_BCC, PREF_ACCOUNT);
9119 if (ac->set_autoreplyto && ac->auto_replyto)
9120 compose_entry_append(compose, ac->auto_replyto,
9121 COMPOSE_REPLYTO, PREF_ACCOUNT);
9123 for (list = saved_list; list; list = list->next) {
9124 state = (HeaderEntryState *) list->data;
9126 compose_entry_append(compose, state->entry,
9127 state->header, state->type);
9128 if (state->entry_marked)
9129 compose_entry_mark_default_to(compose, state->entry);
9131 g_free(state->entry);
9133 g_slist_free(saved_list);
9135 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9136 (ac->protocol == A_NNTP) ?
9137 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9140 /* Set message save folder */
9141 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9142 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9144 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9145 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9147 compose_set_save_to(compose, NULL);
9148 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9149 folderidentifier = folder_item_get_identifier(account_get_special_folder
9150 (compose->account, F_OUTBOX));
9151 compose_set_save_to(compose, folderidentifier);
9152 g_free(folderidentifier);
9156 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9157 GtkTreeViewColumn *column, Compose *compose)
9159 compose_attach_property(NULL, compose);
9162 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9165 Compose *compose = (Compose *)data;
9166 GtkTreeSelection *attach_selection;
9167 gint attach_nr_selected;
9169 if (!event) return FALSE;
9171 if (event->button == 3) {
9172 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9173 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9175 if (attach_nr_selected > 0)
9177 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", TRUE);
9178 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", TRUE);
9180 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
9181 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
9184 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9185 NULL, NULL, event->button, event->time);
9192 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9195 Compose *compose = (Compose *)data;
9197 if (!event) return FALSE;
9199 switch (event->keyval) {
9201 compose_attach_remove_selected(NULL, compose);
9207 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9209 toolbar_comp_set_sensitive(compose, allow);
9210 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9211 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9213 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9215 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9216 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9217 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9219 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9223 static void compose_send_cb(GtkAction *action, gpointer data)
9225 Compose *compose = (Compose *)data;
9227 if (prefs_common.work_offline &&
9228 !inc_offline_should_override(TRUE,
9229 _("Claws Mail needs network access in order "
9230 "to send this email.")))
9233 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9234 g_source_remove(compose->draft_timeout_tag);
9235 compose->draft_timeout_tag = -1;
9238 compose_send(compose);
9241 static void compose_send_later_cb(GtkAction *action, gpointer data)
9243 Compose *compose = (Compose *)data;
9247 compose_allow_user_actions(compose, FALSE);
9248 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9249 compose_allow_user_actions(compose, TRUE);
9253 compose_close(compose);
9254 } else if (val == -1) {
9255 alertpanel_error(_("Could not queue message."));
9256 } else if (val == -2) {
9257 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9258 } else if (val == -3) {
9259 if (privacy_peek_error())
9260 alertpanel_error(_("Could not queue message for sending:\n\n"
9261 "Signature failed: %s"), privacy_get_error());
9262 } else if (val == -4) {
9263 alertpanel_error(_("Could not queue message for sending:\n\n"
9264 "Charset conversion failed."));
9265 } else if (val == -5) {
9266 alertpanel_error(_("Could not queue message for sending:\n\n"
9267 "Couldn't get recipient encryption key."));
9268 } else if (val == -6) {
9271 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9274 #define DRAFTED_AT_EXIT "drafted_at_exit"
9275 static void compose_register_draft(MsgInfo *info)
9277 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9278 DRAFTED_AT_EXIT, NULL);
9279 FILE *fp = g_fopen(filepath, "ab");
9282 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9290 gboolean compose_draft (gpointer data, guint action)
9292 Compose *compose = (Compose *)data;
9296 MsgFlags flag = {0, 0};
9297 static gboolean lock = FALSE;
9298 MsgInfo *newmsginfo;
9300 gboolean target_locked = FALSE;
9301 gboolean err = FALSE;
9303 if (lock) return FALSE;
9305 if (compose->sending)
9308 draft = account_get_special_folder(compose->account, F_DRAFT);
9309 cm_return_val_if_fail(draft != NULL, FALSE);
9311 if (!g_mutex_trylock(compose->mutex)) {
9312 /* we don't want to lock the mutex once it's available,
9313 * because as the only other part of compose.c locking
9314 * it is compose_close - which means once unlocked,
9315 * the compose struct will be freed */
9316 debug_print("couldn't lock mutex, probably sending\n");
9322 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9323 G_DIR_SEPARATOR, compose);
9324 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9325 FILE_OP_ERROR(tmp, "fopen");
9329 /* chmod for security */
9330 if (change_file_mode_rw(fp, tmp) < 0) {
9331 FILE_OP_ERROR(tmp, "chmod");
9332 g_warning("can't change file mode\n");
9335 /* Save draft infos */
9336 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9337 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9339 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9340 gchar *savefolderid;
9342 savefolderid = compose_get_save_to(compose);
9343 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9344 g_free(savefolderid);
9346 if (compose->return_receipt) {
9347 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9349 if (compose->privacy_system) {
9350 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9351 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9352 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9355 /* Message-ID of message replying to */
9356 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9359 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9360 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9363 /* Message-ID of message forwarding to */
9364 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9367 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9368 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9372 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9373 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9375 /* end of headers */
9376 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9383 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9387 if (fclose(fp) == EOF) {
9391 if (compose->targetinfo) {
9392 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9393 flag.perm_flags = target_locked?MSG_LOCKED:0;
9395 flag.tmp_flags = MSG_DRAFT;
9397 folder_item_scan(draft);
9398 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9399 MsgInfo *tmpinfo = NULL;
9400 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9401 if (compose->msgid) {
9402 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9405 msgnum = tmpinfo->msgnum;
9406 procmsg_msginfo_free(tmpinfo);
9407 debug_print("got draft msgnum %d from scanning\n", msgnum);
9409 debug_print("didn't get draft msgnum after scanning\n");
9412 debug_print("got draft msgnum %d from adding\n", msgnum);
9418 if (action != COMPOSE_AUTO_SAVE) {
9419 if (action != COMPOSE_DRAFT_FOR_EXIT)
9420 alertpanel_error(_("Could not save draft."));
9423 gtkut_window_popup(compose->window);
9424 val = alertpanel_full(_("Could not save draft"),
9425 _("Could not save draft.\n"
9426 "Do you want to cancel exit or discard this email?"),
9427 _("_Cancel exit"), _("_Discard email"), NULL,
9428 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9429 if (val == G_ALERTALTERNATE) {
9431 g_mutex_unlock(compose->mutex); /* must be done before closing */
9432 compose_close(compose);
9436 g_mutex_unlock(compose->mutex); /* must be done before closing */
9445 if (compose->mode == COMPOSE_REEDIT) {
9446 compose_remove_reedit_target(compose, TRUE);
9449 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9452 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9454 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9456 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9457 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9458 procmsg_msginfo_set_flags(newmsginfo, 0,
9459 MSG_HAS_ATTACHMENT);
9461 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9462 compose_register_draft(newmsginfo);
9464 procmsg_msginfo_free(newmsginfo);
9467 folder_item_scan(draft);
9469 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9471 g_mutex_unlock(compose->mutex); /* must be done before closing */
9472 compose_close(compose);
9478 path = folder_item_fetch_msg(draft, msgnum);
9480 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9483 if (g_stat(path, &s) < 0) {
9484 FILE_OP_ERROR(path, "stat");
9490 procmsg_msginfo_free(compose->targetinfo);
9491 compose->targetinfo = procmsg_msginfo_new();
9492 compose->targetinfo->msgnum = msgnum;
9493 compose->targetinfo->size = (goffset)s.st_size;
9494 compose->targetinfo->mtime = s.st_mtime;
9495 compose->targetinfo->folder = draft;
9497 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9498 compose->mode = COMPOSE_REEDIT;
9500 if (action == COMPOSE_AUTO_SAVE) {
9501 compose->autosaved_draft = compose->targetinfo;
9503 compose->modified = FALSE;
9504 compose_set_title(compose);
9508 g_mutex_unlock(compose->mutex);
9512 void compose_clear_exit_drafts(void)
9514 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9515 DRAFTED_AT_EXIT, NULL);
9516 if (is_file_exist(filepath))
9517 claws_unlink(filepath);
9522 void compose_reopen_exit_drafts(void)
9524 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9525 DRAFTED_AT_EXIT, NULL);
9526 FILE *fp = g_fopen(filepath, "rb");
9530 while (fgets(buf, sizeof(buf), fp)) {
9531 gchar **parts = g_strsplit(buf, "\t", 2);
9532 const gchar *folder = parts[0];
9533 int msgnum = parts[1] ? atoi(parts[1]):-1;
9535 if (folder && *folder && msgnum > -1) {
9536 FolderItem *item = folder_find_item_from_identifier(folder);
9537 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9539 compose_reedit(info, FALSE);
9546 compose_clear_exit_drafts();
9549 static void compose_save_cb(GtkAction *action, gpointer data)
9551 Compose *compose = (Compose *)data;
9552 compose_draft(compose, COMPOSE_KEEP_EDITING);
9553 compose->rmode = COMPOSE_REEDIT;
9556 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9558 if (compose && file_list) {
9561 for ( tmp = file_list; tmp; tmp = tmp->next) {
9562 gchar *file = (gchar *) tmp->data;
9563 gchar *utf8_filename = conv_filename_to_utf8(file);
9564 compose_attach_append(compose, file, utf8_filename, NULL);
9565 compose_changed_cb(NULL, compose);
9570 g_free(utf8_filename);
9575 static void compose_attach_cb(GtkAction *action, gpointer data)
9577 Compose *compose = (Compose *)data;
9580 if (compose->redirect_filename != NULL)
9583 file_list = filesel_select_multiple_files_open(_("Select file"));
9586 compose_attach_from_list(compose, file_list, TRUE);
9587 g_list_free(file_list);
9591 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9593 Compose *compose = (Compose *)data;
9595 gint files_inserted = 0;
9597 file_list = filesel_select_multiple_files_open(_("Select file"));
9602 for ( tmp = file_list; tmp; tmp = tmp->next) {
9603 gchar *file = (gchar *) tmp->data;
9604 gchar *filedup = g_strdup(file);
9605 gchar *shortfile = g_path_get_basename(filedup);
9606 ComposeInsertResult res;
9607 /* insert the file if the file is short or if the user confirmed that
9608 he/she wants to insert the large file */
9609 res = compose_insert_file(compose, file);
9610 if (res == COMPOSE_INSERT_READ_ERROR) {
9611 alertpanel_error(_("File '%s' could not be read."), shortfile);
9612 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9613 alertpanel_error(_("File '%s' contained invalid characters\n"
9614 "for the current encoding, insertion may be incorrect."),
9616 } else if (res == COMPOSE_INSERT_SUCCESS)
9623 g_list_free(file_list);
9627 if (files_inserted > 0 && compose->gtkaspell &&
9628 compose->gtkaspell->check_while_typing)
9629 gtkaspell_highlight_all(compose->gtkaspell);
9633 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9635 Compose *compose = (Compose *)data;
9637 compose_insert_sig(compose, FALSE);
9640 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9644 Compose *compose = (Compose *)data;
9646 gtkut_widget_get_uposition(widget, &x, &y);
9647 if (!compose->batch) {
9648 prefs_common.compose_x = x;
9649 prefs_common.compose_y = y;
9651 if (compose->sending || compose->updating)
9653 compose_close_cb(NULL, compose);
9657 void compose_close_toolbar(Compose *compose)
9659 compose_close_cb(NULL, compose);
9662 static void compose_close_cb(GtkAction *action, gpointer data)
9664 Compose *compose = (Compose *)data;
9668 if (compose->exteditor_tag != -1) {
9669 if (!compose_ext_editor_kill(compose))
9674 if (compose->modified) {
9675 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
9676 if (!g_mutex_trylock(compose->mutex)) {
9677 /* we don't want to lock the mutex once it's available,
9678 * because as the only other part of compose.c locking
9679 * it is compose_close - which means once unlocked,
9680 * the compose struct will be freed */
9681 debug_print("couldn't lock mutex, probably sending\n");
9685 val = alertpanel(_("Discard message"),
9686 _("This message has been modified. Discard it?"),
9687 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9689 val = alertpanel(_("Save changes"),
9690 _("This message has been modified. Save the latest changes?"),
9691 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
9693 g_mutex_unlock(compose->mutex);
9695 case G_ALERTDEFAULT:
9696 if (prefs_common.autosave && !reedit)
9697 compose_remove_draft(compose);
9699 case G_ALERTALTERNATE:
9700 compose_draft(data, COMPOSE_QUIT_EDITING);
9707 compose_close(compose);
9710 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
9712 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
9713 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
9714 Compose *compose = (Compose *) data;
9717 compose->out_encoding = (CharSet)value;
9720 static void compose_address_cb(GtkAction *action, gpointer data)
9722 Compose *compose = (Compose *)data;
9724 addressbook_open(compose);
9727 static void about_show_cb(GtkAction *action, gpointer data)
9732 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9734 Compose *compose = (Compose *)data;
9739 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9740 cm_return_if_fail(tmpl != NULL);
9742 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9744 val = alertpanel(_("Apply template"), msg,
9745 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9748 if (val == G_ALERTDEFAULT)
9749 compose_template_apply(compose, tmpl, TRUE);
9750 else if (val == G_ALERTALTERNATE)
9751 compose_template_apply(compose, tmpl, FALSE);
9754 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
9756 Compose *compose = (Compose *)data;
9758 compose_exec_ext_editor(compose);
9761 static void compose_undo_cb(GtkAction *action, gpointer data)
9763 Compose *compose = (Compose *)data;
9764 gboolean prev_autowrap = compose->autowrap;
9766 compose->autowrap = FALSE;
9767 undo_undo(compose->undostruct);
9768 compose->autowrap = prev_autowrap;
9771 static void compose_redo_cb(GtkAction *action, gpointer data)
9773 Compose *compose = (Compose *)data;
9774 gboolean prev_autowrap = compose->autowrap;
9776 compose->autowrap = FALSE;
9777 undo_redo(compose->undostruct);
9778 compose->autowrap = prev_autowrap;
9781 static void entry_cut_clipboard(GtkWidget *entry)
9783 if (GTK_IS_EDITABLE(entry))
9784 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9785 else if (GTK_IS_TEXT_VIEW(entry))
9786 gtk_text_buffer_cut_clipboard(
9787 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9788 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9792 static void entry_copy_clipboard(GtkWidget *entry)
9794 if (GTK_IS_EDITABLE(entry))
9795 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9796 else if (GTK_IS_TEXT_VIEW(entry))
9797 gtk_text_buffer_copy_clipboard(
9798 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9799 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9802 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9803 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9805 if (GTK_IS_TEXT_VIEW(entry)) {
9806 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9807 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9808 GtkTextIter start_iter, end_iter;
9810 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9812 if (contents == NULL)
9815 /* we shouldn't delete the selection when middle-click-pasting, or we
9816 * can't mid-click-paste our own selection */
9817 if (clip != GDK_SELECTION_PRIMARY) {
9818 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9819 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9822 if (insert_place == NULL) {
9823 /* if insert_place isn't specified, insert at the cursor.
9824 * used for Ctrl-V pasting */
9825 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9826 start = gtk_text_iter_get_offset(&start_iter);
9827 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9829 /* if insert_place is specified, paste here.
9830 * used for mid-click-pasting */
9831 start = gtk_text_iter_get_offset(insert_place);
9832 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9833 if (prefs_common.primary_paste_unselects)
9834 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
9838 /* paste unwrapped: mark the paste so it's not wrapped later */
9839 end = start + strlen(contents);
9840 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9841 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9842 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9843 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9844 /* rewrap paragraph now (after a mid-click-paste) */
9845 mark_start = gtk_text_buffer_get_insert(buffer);
9846 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9847 gtk_text_iter_backward_char(&start_iter);
9848 compose_beautify_paragraph(compose, &start_iter, TRUE);
9850 } else if (GTK_IS_EDITABLE(entry))
9851 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9853 compose->modified = TRUE;
9856 static void entry_allsel(GtkWidget *entry)
9858 if (GTK_IS_EDITABLE(entry))
9859 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9860 else if (GTK_IS_TEXT_VIEW(entry)) {
9861 GtkTextIter startiter, enditer;
9862 GtkTextBuffer *textbuf;
9864 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9865 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9866 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9868 gtk_text_buffer_move_mark_by_name(textbuf,
9869 "selection_bound", &startiter);
9870 gtk_text_buffer_move_mark_by_name(textbuf,
9871 "insert", &enditer);
9875 static void compose_cut_cb(GtkAction *action, gpointer data)
9877 Compose *compose = (Compose *)data;
9878 if (compose->focused_editable
9879 #ifndef GENERIC_UMPC
9880 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9883 entry_cut_clipboard(compose->focused_editable);
9886 static void compose_copy_cb(GtkAction *action, gpointer data)
9888 Compose *compose = (Compose *)data;
9889 if (compose->focused_editable
9890 #ifndef GENERIC_UMPC
9891 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9894 entry_copy_clipboard(compose->focused_editable);
9897 static void compose_paste_cb(GtkAction *action, gpointer data)
9899 Compose *compose = (Compose *)data;
9901 GtkTextBuffer *buffer;
9903 if (compose->focused_editable &&
9904 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9905 entry_paste_clipboard(compose, compose->focused_editable,
9906 prefs_common.linewrap_pastes,
9907 GDK_SELECTION_CLIPBOARD, NULL);
9911 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9912 compose->gtkaspell &&
9913 compose->gtkaspell->check_while_typing)
9914 gtkaspell_highlight_all(compose->gtkaspell);
9918 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
9920 Compose *compose = (Compose *)data;
9921 gint wrap_quote = prefs_common.linewrap_quote;
9922 if (compose->focused_editable
9923 #ifndef GENERIC_UMPC
9924 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9927 /* let text_insert() (called directly or at a later time
9928 * after the gtk_editable_paste_clipboard) know that
9929 * text is to be inserted as a quotation. implemented
9930 * by using a simple refcount... */
9931 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9932 G_OBJECT(compose->focused_editable),
9933 "paste_as_quotation"));
9934 g_object_set_data(G_OBJECT(compose->focused_editable),
9935 "paste_as_quotation",
9936 GINT_TO_POINTER(paste_as_quotation + 1));
9937 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9938 entry_paste_clipboard(compose, compose->focused_editable,
9939 prefs_common.linewrap_pastes,
9940 GDK_SELECTION_CLIPBOARD, NULL);
9941 prefs_common.linewrap_quote = wrap_quote;
9945 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
9947 Compose *compose = (Compose *)data;
9949 GtkTextBuffer *buffer;
9951 if (compose->focused_editable
9952 #ifndef GENERIC_UMPC
9953 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9956 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9957 GDK_SELECTION_CLIPBOARD, NULL);
9961 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9962 compose->gtkaspell &&
9963 compose->gtkaspell->check_while_typing)
9964 gtkaspell_highlight_all(compose->gtkaspell);
9968 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
9970 Compose *compose = (Compose *)data;
9972 GtkTextBuffer *buffer;
9974 if (compose->focused_editable
9975 #ifndef GENERIC_UMPC
9976 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9979 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9980 GDK_SELECTION_CLIPBOARD, NULL);
9984 if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
9985 compose->gtkaspell &&
9986 compose->gtkaspell->check_while_typing)
9987 gtkaspell_highlight_all(compose->gtkaspell);
9991 static void compose_allsel_cb(GtkAction *action, gpointer data)
9993 Compose *compose = (Compose *)data;
9994 if (compose->focused_editable
9995 #ifndef GENERIC_UMPC
9996 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9999 entry_allsel(compose->focused_editable);
10002 static void textview_move_beginning_of_line (GtkTextView *text)
10004 GtkTextBuffer *buffer;
10008 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10010 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10011 mark = gtk_text_buffer_get_insert(buffer);
10012 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10013 gtk_text_iter_set_line_offset(&ins, 0);
10014 gtk_text_buffer_place_cursor(buffer, &ins);
10017 static void textview_move_forward_character (GtkTextView *text)
10019 GtkTextBuffer *buffer;
10023 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10025 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10026 mark = gtk_text_buffer_get_insert(buffer);
10027 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10028 if (gtk_text_iter_forward_cursor_position(&ins))
10029 gtk_text_buffer_place_cursor(buffer, &ins);
10032 static void textview_move_backward_character (GtkTextView *text)
10034 GtkTextBuffer *buffer;
10038 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10040 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10041 mark = gtk_text_buffer_get_insert(buffer);
10042 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10043 if (gtk_text_iter_backward_cursor_position(&ins))
10044 gtk_text_buffer_place_cursor(buffer, &ins);
10047 static void textview_move_forward_word (GtkTextView *text)
10049 GtkTextBuffer *buffer;
10054 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10056 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10057 mark = gtk_text_buffer_get_insert(buffer);
10058 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10059 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10060 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10061 gtk_text_iter_backward_word_start(&ins);
10062 gtk_text_buffer_place_cursor(buffer, &ins);
10066 static void textview_move_backward_word (GtkTextView *text)
10068 GtkTextBuffer *buffer;
10073 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10075 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10076 mark = gtk_text_buffer_get_insert(buffer);
10077 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10078 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10079 if (gtk_text_iter_backward_word_starts(&ins, 1))
10080 gtk_text_buffer_place_cursor(buffer, &ins);
10083 static void textview_move_end_of_line (GtkTextView *text)
10085 GtkTextBuffer *buffer;
10089 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10091 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10092 mark = gtk_text_buffer_get_insert(buffer);
10093 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10094 if (gtk_text_iter_forward_to_line_end(&ins))
10095 gtk_text_buffer_place_cursor(buffer, &ins);
10098 static void textview_move_next_line (GtkTextView *text)
10100 GtkTextBuffer *buffer;
10105 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10107 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10108 mark = gtk_text_buffer_get_insert(buffer);
10109 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10110 offset = gtk_text_iter_get_line_offset(&ins);
10111 if (gtk_text_iter_forward_line(&ins)) {
10112 gtk_text_iter_set_line_offset(&ins, offset);
10113 gtk_text_buffer_place_cursor(buffer, &ins);
10117 static void textview_move_previous_line (GtkTextView *text)
10119 GtkTextBuffer *buffer;
10124 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10126 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10127 mark = gtk_text_buffer_get_insert(buffer);
10128 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10129 offset = gtk_text_iter_get_line_offset(&ins);
10130 if (gtk_text_iter_backward_line(&ins)) {
10131 gtk_text_iter_set_line_offset(&ins, offset);
10132 gtk_text_buffer_place_cursor(buffer, &ins);
10136 static void textview_delete_forward_character (GtkTextView *text)
10138 GtkTextBuffer *buffer;
10140 GtkTextIter ins, end_iter;
10142 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10144 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10145 mark = gtk_text_buffer_get_insert(buffer);
10146 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10148 if (gtk_text_iter_forward_char(&end_iter)) {
10149 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10153 static void textview_delete_backward_character (GtkTextView *text)
10155 GtkTextBuffer *buffer;
10157 GtkTextIter ins, end_iter;
10159 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10161 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10162 mark = gtk_text_buffer_get_insert(buffer);
10163 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10165 if (gtk_text_iter_backward_char(&end_iter)) {
10166 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10170 static void textview_delete_forward_word (GtkTextView *text)
10172 GtkTextBuffer *buffer;
10174 GtkTextIter ins, end_iter;
10176 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10178 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10179 mark = gtk_text_buffer_get_insert(buffer);
10180 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10182 if (gtk_text_iter_forward_word_end(&end_iter)) {
10183 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10187 static void textview_delete_backward_word (GtkTextView *text)
10189 GtkTextBuffer *buffer;
10191 GtkTextIter ins, end_iter;
10193 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10195 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10196 mark = gtk_text_buffer_get_insert(buffer);
10197 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10199 if (gtk_text_iter_backward_word_start(&end_iter)) {
10200 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10204 static void textview_delete_line (GtkTextView *text)
10206 GtkTextBuffer *buffer;
10208 GtkTextIter ins, start_iter, end_iter;
10210 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10212 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10213 mark = gtk_text_buffer_get_insert(buffer);
10214 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10217 gtk_text_iter_set_line_offset(&start_iter, 0);
10220 if (gtk_text_iter_ends_line(&end_iter)){
10221 if (!gtk_text_iter_forward_char(&end_iter))
10222 gtk_text_iter_backward_char(&start_iter);
10225 gtk_text_iter_forward_to_line_end(&end_iter);
10226 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10229 static void textview_delete_to_line_end (GtkTextView *text)
10231 GtkTextBuffer *buffer;
10233 GtkTextIter ins, end_iter;
10235 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10237 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10238 mark = gtk_text_buffer_get_insert(buffer);
10239 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10241 if (gtk_text_iter_ends_line(&end_iter))
10242 gtk_text_iter_forward_char(&end_iter);
10244 gtk_text_iter_forward_to_line_end(&end_iter);
10245 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10248 #define DO_ACTION(name, act) { \
10249 if(!strcmp(name, a_name)) { \
10253 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10255 const gchar *a_name = gtk_action_get_name(action);
10256 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10257 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10258 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10259 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10260 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10261 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10262 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10263 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10264 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10265 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10266 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10267 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10268 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10269 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10273 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10275 Compose *compose = (Compose *)data;
10276 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10277 ComposeCallAdvancedAction action = -1;
10279 action = compose_call_advanced_action_from_path(gaction);
10282 void (*do_action) (GtkTextView *text);
10283 } action_table[] = {
10284 {textview_move_beginning_of_line},
10285 {textview_move_forward_character},
10286 {textview_move_backward_character},
10287 {textview_move_forward_word},
10288 {textview_move_backward_word},
10289 {textview_move_end_of_line},
10290 {textview_move_next_line},
10291 {textview_move_previous_line},
10292 {textview_delete_forward_character},
10293 {textview_delete_backward_character},
10294 {textview_delete_forward_word},
10295 {textview_delete_backward_word},
10296 {textview_delete_line},
10297 {textview_delete_to_line_end}
10300 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
10302 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10303 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10304 if (action_table[action].do_action)
10305 action_table[action].do_action(text);
10307 g_warning("Not implemented yet.");
10311 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10315 if (GTK_IS_EDITABLE(widget)) {
10316 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10317 gtk_editable_set_position(GTK_EDITABLE(widget),
10320 if (widget->parent && widget->parent->parent
10321 && widget->parent->parent->parent) {
10322 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
10323 gint y = widget->allocation.y;
10324 gint height = widget->allocation.height;
10325 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10326 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
10328 if (y < (int)shown->value) {
10329 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
10331 if (y + height > (int)shown->value + (int)shown->page_size) {
10332 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
10333 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10334 y + height - (int)shown->page_size - 1);
10336 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
10337 (int)shown->upper - (int)shown->page_size - 1);
10344 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10345 compose->focused_editable = widget;
10347 #ifdef GENERIC_UMPC
10348 if (GTK_IS_TEXT_VIEW(widget)
10349 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10350 g_object_ref(compose->notebook);
10351 g_object_ref(compose->edit_vbox);
10352 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10353 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10354 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10355 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10356 g_object_unref(compose->notebook);
10357 g_object_unref(compose->edit_vbox);
10358 g_signal_handlers_block_by_func(G_OBJECT(widget),
10359 G_CALLBACK(compose_grab_focus_cb),
10361 gtk_widget_grab_focus(widget);
10362 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10363 G_CALLBACK(compose_grab_focus_cb),
10365 } else if (!GTK_IS_TEXT_VIEW(widget)
10366 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10367 g_object_ref(compose->notebook);
10368 g_object_ref(compose->edit_vbox);
10369 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10370 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10371 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10372 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10373 g_object_unref(compose->notebook);
10374 g_object_unref(compose->edit_vbox);
10375 g_signal_handlers_block_by_func(G_OBJECT(widget),
10376 G_CALLBACK(compose_grab_focus_cb),
10378 gtk_widget_grab_focus(widget);
10379 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10380 G_CALLBACK(compose_grab_focus_cb),
10386 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10388 compose->modified = TRUE;
10389 // compose_beautify_paragraph(compose, NULL, TRUE);
10390 #ifndef GENERIC_UMPC
10391 compose_set_title(compose);
10395 static void compose_wrap_cb(GtkAction *action, gpointer data)
10397 Compose *compose = (Compose *)data;
10398 compose_beautify_paragraph(compose, NULL, TRUE);
10401 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10403 Compose *compose = (Compose *)data;
10404 compose_wrap_all_full(compose, TRUE);
10407 static void compose_find_cb(GtkAction *action, gpointer data)
10409 Compose *compose = (Compose *)data;
10411 message_search_compose(compose);
10414 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10417 Compose *compose = (Compose *)data;
10418 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10419 if (compose->autowrap)
10420 compose_wrap_all_full(compose, TRUE);
10421 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10424 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10427 Compose *compose = (Compose *)data;
10428 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10431 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10433 Compose *compose = (Compose *)data;
10435 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10438 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10440 Compose *compose = (Compose *)data;
10442 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10445 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10447 g_free(compose->privacy_system);
10449 compose->privacy_system = g_strdup(account->default_privacy_system);
10450 compose_update_privacy_system_menu_item(compose, warn);
10453 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10455 Compose *compose = (Compose *)data;
10457 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10458 gtk_widget_show(compose->ruler_hbox);
10459 prefs_common.show_ruler = TRUE;
10461 gtk_widget_hide(compose->ruler_hbox);
10462 gtk_widget_queue_resize(compose->edit_vbox);
10463 prefs_common.show_ruler = FALSE;
10467 static void compose_attach_drag_received_cb (GtkWidget *widget,
10468 GdkDragContext *context,
10471 GtkSelectionData *data,
10474 gpointer user_data)
10476 Compose *compose = (Compose *)user_data;
10479 if (((gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list"))
10481 || (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND"))
10483 ) && gtk_drag_get_source_widget(context) !=
10484 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10485 list = uri_list_extract_filenames((const gchar *)data->data);
10486 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10487 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10488 compose_attach_append
10489 (compose, (const gchar *)tmp->data,
10490 utf8_filename, NULL);
10491 g_free(utf8_filename);
10493 if (list) compose_changed_cb(NULL, compose);
10494 list_free_strings(list);
10496 } else if (gtk_drag_get_source_widget(context)
10497 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10498 /* comes from our summaryview */
10499 SummaryView * summaryview = NULL;
10500 GSList * list = NULL, *cur = NULL;
10502 if (mainwindow_get_mainwindow())
10503 summaryview = mainwindow_get_mainwindow()->summaryview;
10506 list = summary_get_selected_msg_list(summaryview);
10508 for (cur = list; cur; cur = cur->next) {
10509 MsgInfo *msginfo = (MsgInfo *)cur->data;
10510 gchar *file = NULL;
10512 file = procmsg_get_message_file_full(msginfo,
10515 compose_attach_append(compose, (const gchar *)file,
10516 (const gchar *)file, "message/rfc822");
10520 g_slist_free(list);
10524 static gboolean compose_drag_drop(GtkWidget *widget,
10525 GdkDragContext *drag_context,
10527 guint time, gpointer user_data)
10529 /* not handling this signal makes compose_insert_drag_received_cb
10534 static void compose_insert_drag_received_cb (GtkWidget *widget,
10535 GdkDragContext *drag_context,
10538 GtkSelectionData *data,
10541 gpointer user_data)
10543 Compose *compose = (Compose *)user_data;
10546 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10549 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
10551 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "DROPFILES_DND")) {
10553 AlertValue val = G_ALERTDEFAULT;
10555 list = uri_list_extract_filenames((const gchar *)data->data);
10556 if (list == NULL && strstr((gchar *)(data->data), "://")) {
10557 /* Assume a list of no files, and data has ://, is a remote link */
10558 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
10559 gchar *tmpfile = get_tmp_file();
10560 str_write_to_file(tmpdata, tmpfile);
10562 compose_insert_file(compose, tmpfile);
10563 claws_unlink(tmpfile);
10565 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10566 compose_beautify_paragraph(compose, NULL, TRUE);
10569 switch (prefs_common.compose_dnd_mode) {
10570 case COMPOSE_DND_ASK:
10571 val = alertpanel_full(_("Insert or attach?"),
10572 _("Do you want to insert the contents of the file(s) "
10573 "into the message body, or attach it to the email?"),
10574 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10575 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10577 case COMPOSE_DND_INSERT:
10578 val = G_ALERTALTERNATE;
10580 case COMPOSE_DND_ATTACH:
10581 val = G_ALERTOTHER;
10584 /* unexpected case */
10585 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10588 if (val & G_ALERTDISABLE) {
10589 val &= ~G_ALERTDISABLE;
10590 /* remember what action to perform by default, only if we don't click Cancel */
10591 if (val == G_ALERTALTERNATE)
10592 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10593 else if (val == G_ALERTOTHER)
10594 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10597 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10598 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10599 list_free_strings(list);
10602 } else if (val == G_ALERTOTHER) {
10603 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10604 list_free_strings(list);
10609 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10610 compose_insert_file(compose, (const gchar *)tmp->data);
10612 list_free_strings(list);
10614 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10617 #if GTK_CHECK_VERSION(2, 8, 0)
10618 /* do nothing, handled by GTK */
10620 gchar *tmpfile = get_tmp_file();
10621 str_write_to_file((const gchar *)data->data, tmpfile);
10622 compose_insert_file(compose, tmpfile);
10623 claws_unlink(tmpfile);
10625 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10629 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10632 static void compose_header_drag_received_cb (GtkWidget *widget,
10633 GdkDragContext *drag_context,
10636 GtkSelectionData *data,
10639 gpointer user_data)
10641 GtkEditable *entry = (GtkEditable *)user_data;
10642 gchar *email = (gchar *)data->data;
10644 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10647 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10648 gchar *decoded=g_new(gchar, strlen(email));
10651 email += strlen("mailto:");
10652 decode_uri(decoded, email); /* will fit */
10653 gtk_editable_delete_text(entry, 0, -1);
10654 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10655 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10659 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10662 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
10664 Compose *compose = (Compose *)data;
10666 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10667 compose->return_receipt = TRUE;
10669 compose->return_receipt = FALSE;
10672 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
10674 Compose *compose = (Compose *)data;
10676 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
10677 compose->remove_references = TRUE;
10679 compose->remove_references = FALSE;
10682 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
10683 ComposeHeaderEntry *headerentry)
10685 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
10689 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10690 GdkEventKey *event,
10691 ComposeHeaderEntry *headerentry)
10693 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10694 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10695 !(event->state & GDK_MODIFIER_MASK) &&
10696 (event->keyval == GDK_BackSpace) &&
10697 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10698 gtk_container_remove
10699 (GTK_CONTAINER(headerentry->compose->header_table),
10700 headerentry->combo);
10701 gtk_container_remove
10702 (GTK_CONTAINER(headerentry->compose->header_table),
10703 headerentry->entry);
10704 headerentry->compose->header_list =
10705 g_slist_remove(headerentry->compose->header_list,
10707 g_free(headerentry);
10708 } else if (event->keyval == GDK_Tab) {
10709 if (headerentry->compose->header_last == headerentry) {
10710 /* Override default next focus, and give it to subject_entry
10711 * instead of notebook tabs
10713 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10714 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10721 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10722 ComposeHeaderEntry *headerentry)
10724 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10725 compose_create_header_entry(headerentry->compose);
10726 g_signal_handlers_disconnect_matched
10727 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10728 0, 0, NULL, NULL, headerentry);
10730 /* Automatically scroll down */
10731 GTK_EVENTS_FLUSH();
10732 compose_show_first_last_header(headerentry->compose, FALSE);
10738 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10740 GtkAdjustment *vadj;
10742 cm_return_if_fail(compose);
10743 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10744 cm_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10745 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10746 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : (vadj->upper - vadj->page_size)));
10747 gtk_adjustment_changed(vadj);
10750 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10751 const gchar *text, gint len, Compose *compose)
10753 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10754 (G_OBJECT(compose->text), "paste_as_quotation"));
10757 cm_return_if_fail(text != NULL);
10759 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10760 G_CALLBACK(text_inserted),
10762 if (paste_as_quotation) {
10764 const gchar *qmark;
10766 GtkTextIter start_iter;
10769 len = strlen(text);
10771 new_text = g_strndup(text, len);
10773 qmark = compose_quote_char_from_context(compose);
10775 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10776 gtk_text_buffer_place_cursor(buffer, iter);
10778 pos = gtk_text_iter_get_offset(iter);
10780 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10781 _("Quote format error at line %d."));
10782 quote_fmt_reset_vartable();
10784 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10785 GINT_TO_POINTER(paste_as_quotation - 1));
10787 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10788 gtk_text_buffer_place_cursor(buffer, iter);
10789 gtk_text_buffer_delete_mark(buffer, mark);
10791 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10792 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10793 compose_beautify_paragraph(compose, &start_iter, FALSE);
10794 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10795 gtk_text_buffer_delete_mark(buffer, mark);
10797 if (strcmp(text, "\n") || compose->automatic_break
10798 || gtk_text_iter_starts_line(iter)) {
10799 GtkTextIter before_ins;
10800 gtk_text_buffer_insert(buffer, iter, text, len);
10801 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
10802 before_ins = *iter;
10803 gtk_text_iter_backward_chars(&before_ins, len);
10804 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
10807 /* check if the preceding is just whitespace or quote */
10808 GtkTextIter start_line;
10809 gchar *tmp = NULL, *quote = NULL;
10810 gint quote_len = 0, is_normal = 0;
10811 start_line = *iter;
10812 gtk_text_iter_set_line_offset(&start_line, 0);
10813 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10816 if (*tmp == '\0') {
10819 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10827 gtk_text_buffer_insert(buffer, iter, text, len);
10829 gtk_text_buffer_insert_with_tags_by_name(buffer,
10830 iter, text, len, "no_join", NULL);
10835 if (!paste_as_quotation) {
10836 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10837 compose_beautify_paragraph(compose, iter, FALSE);
10838 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10839 gtk_text_buffer_delete_mark(buffer, mark);
10842 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10843 G_CALLBACK(text_inserted),
10845 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10847 if (prefs_common.autosave &&
10848 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10849 compose->draft_timeout_tag != -2 /* disabled while loading */)
10850 compose->draft_timeout_tag = g_timeout_add
10851 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10853 static gint compose_defer_auto_save_draft(Compose *compose)
10855 compose->draft_timeout_tag = -1;
10856 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10861 static void compose_check_all(GtkAction *action, gpointer data)
10863 Compose *compose = (Compose *)data;
10864 if (!compose->gtkaspell)
10867 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10868 claws_spell_entry_check_all(
10869 CLAWS_SPELL_ENTRY(compose->subject_entry));
10871 gtkaspell_check_all(compose->gtkaspell);
10874 static void compose_highlight_all(GtkAction *action, gpointer data)
10876 Compose *compose = (Compose *)data;
10877 if (compose->gtkaspell) {
10878 claws_spell_entry_recheck_all(
10879 CLAWS_SPELL_ENTRY(compose->subject_entry));
10880 gtkaspell_highlight_all(compose->gtkaspell);
10884 static void compose_check_backwards(GtkAction *action, gpointer data)
10886 Compose *compose = (Compose *)data;
10887 if (!compose->gtkaspell) {
10888 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10892 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10893 claws_spell_entry_check_backwards(
10894 CLAWS_SPELL_ENTRY(compose->subject_entry));
10896 gtkaspell_check_backwards(compose->gtkaspell);
10899 static void compose_check_forwards_go(GtkAction *action, gpointer data)
10901 Compose *compose = (Compose *)data;
10902 if (!compose->gtkaspell) {
10903 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
10907 if (GTK_WIDGET_HAS_FOCUS(compose->subject_entry))
10908 claws_spell_entry_check_forwards_go(
10909 CLAWS_SPELL_ENTRY(compose->subject_entry));
10911 gtkaspell_check_forwards_go(compose->gtkaspell);
10916 *\brief Guess originating forward account from MsgInfo and several
10917 * "common preference" settings. Return NULL if no guess.
10919 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10921 PrefsAccount *account = NULL;
10923 cm_return_val_if_fail(msginfo, NULL);
10924 cm_return_val_if_fail(msginfo->folder, NULL);
10925 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
10927 if (msginfo->folder->prefs->enable_default_account)
10928 account = account_find_from_id(msginfo->folder->prefs->default_account);
10931 account = msginfo->folder->folder->account;
10933 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10935 Xstrdup_a(to, msginfo->to, return NULL);
10936 extract_address(to);
10937 account = account_find_from_address(to, FALSE);
10940 if (!account && prefs_common.forward_account_autosel) {
10941 gchar cc[BUFFSIZE];
10942 if (!procheader_get_header_from_msginfo
10943 (msginfo, cc,sizeof cc , "Cc:")) {
10944 gchar *buf = cc + strlen("Cc:");
10945 extract_address(buf);
10946 account = account_find_from_address(buf, FALSE);
10950 if (!account && prefs_common.forward_account_autosel) {
10951 gchar deliveredto[BUFFSIZE];
10952 if (!procheader_get_header_from_msginfo
10953 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10954 gchar *buf = deliveredto + strlen("Delivered-To:");
10955 extract_address(buf);
10956 account = account_find_from_address(buf, FALSE);
10963 gboolean compose_close(Compose *compose)
10967 if (!g_mutex_trylock(compose->mutex)) {
10968 /* we have to wait for the (possibly deferred by auto-save)
10969 * drafting to be done, before destroying the compose under
10971 debug_print("waiting for drafting to finish...\n");
10972 compose_allow_user_actions(compose, FALSE);
10973 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10976 cm_return_val_if_fail(compose, FALSE);
10977 gtkut_widget_get_uposition(compose->window, &x, &y);
10978 if (!compose->batch) {
10979 prefs_common.compose_x = x;
10980 prefs_common.compose_y = y;
10982 g_mutex_unlock(compose->mutex);
10983 compose_destroy(compose);
10988 * Add entry field for each address in list.
10989 * \param compose E-Mail composition object.
10990 * \param listAddress List of (formatted) E-Mail addresses.
10992 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10995 node = listAddress;
10997 addr = ( gchar * ) node->data;
10998 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
10999 node = g_list_next( node );
11003 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11004 guint action, gboolean opening_multiple)
11006 gchar *body = NULL;
11007 GSList *new_msglist = NULL;
11008 MsgInfo *tmp_msginfo = NULL;
11009 gboolean originally_enc = FALSE;
11010 gboolean originally_sig = FALSE;
11011 Compose *compose = NULL;
11012 gchar *s_system = NULL;
11014 cm_return_if_fail(msgview != NULL);
11016 cm_return_if_fail(msginfo_list != NULL);
11018 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11019 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11020 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11022 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11023 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11024 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11025 orig_msginfo, mimeinfo);
11026 if (tmp_msginfo != NULL) {
11027 new_msglist = g_slist_append(NULL, tmp_msginfo);
11029 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11030 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11031 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11033 tmp_msginfo->folder = orig_msginfo->folder;
11034 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11035 if (orig_msginfo->tags) {
11036 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11037 tmp_msginfo->folder->tags_dirty = TRUE;
11043 if (!opening_multiple)
11044 body = messageview_get_selection(msgview);
11047 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11048 procmsg_msginfo_free(tmp_msginfo);
11049 g_slist_free(new_msglist);
11051 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11053 if (compose && originally_enc) {
11054 compose_force_encryption(compose, compose->account, FALSE, s_system);
11057 if (compose && originally_sig && compose->account->default_sign_reply) {
11058 compose_force_signing(compose, compose->account, s_system);
11064 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11067 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11068 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11069 GSList *cur = msginfo_list;
11070 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11071 "messages. Opening the windows "
11072 "could take some time. Do you "
11073 "want to continue?"),
11074 g_slist_length(msginfo_list));
11075 if (g_slist_length(msginfo_list) > 9
11076 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11077 != G_ALERTALTERNATE) {
11082 /* We'll open multiple compose windows */
11083 /* let the WM place the next windows */
11084 compose_force_window_origin = FALSE;
11085 for (; cur; cur = cur->next) {
11087 tmplist.data = cur->data;
11088 tmplist.next = NULL;
11089 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11091 compose_force_window_origin = TRUE;
11093 /* forwarding multiple mails as attachments is done via a
11094 * single compose window */
11095 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11099 void compose_set_position(Compose *compose, gint pos)
11101 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11103 gtkut_text_view_set_position(text, pos);
11106 gboolean compose_search_string(Compose *compose,
11107 const gchar *str, gboolean case_sens)
11109 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11111 return gtkut_text_view_search_string(text, str, case_sens);
11114 gboolean compose_search_string_backward(Compose *compose,
11115 const gchar *str, gboolean case_sens)
11117 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11119 return gtkut_text_view_search_string_backward(text, str, case_sens);
11122 /* allocate a msginfo structure and populate its data from a compose data structure */
11123 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11125 MsgInfo *newmsginfo;
11127 gchar buf[BUFFSIZE];
11129 cm_return_val_if_fail( compose != NULL, NULL );
11131 newmsginfo = procmsg_msginfo_new();
11134 get_rfc822_date(buf, sizeof(buf));
11135 newmsginfo->date = g_strdup(buf);
11138 if (compose->from_name) {
11139 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11140 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11144 if (compose->subject_entry)
11145 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11147 /* to, cc, reply-to, newsgroups */
11148 for (list = compose->header_list; list; list = list->next) {
11149 gchar *header = gtk_editable_get_chars(
11151 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11152 gchar *entry = gtk_editable_get_chars(
11153 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11155 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11156 if ( newmsginfo->to == NULL ) {
11157 newmsginfo->to = g_strdup(entry);
11158 } else if (entry && *entry) {
11159 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11160 g_free(newmsginfo->to);
11161 newmsginfo->to = tmp;
11164 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11165 if ( newmsginfo->cc == NULL ) {
11166 newmsginfo->cc = g_strdup(entry);
11167 } else if (entry && *entry) {
11168 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11169 g_free(newmsginfo->cc);
11170 newmsginfo->cc = tmp;
11173 if ( strcasecmp(header,
11174 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11175 if ( newmsginfo->newsgroups == NULL ) {
11176 newmsginfo->newsgroups = g_strdup(entry);
11177 } else if (entry && *entry) {
11178 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11179 g_free(newmsginfo->newsgroups);
11180 newmsginfo->newsgroups = tmp;
11188 /* other data is unset */
11194 /* update compose's dictionaries from folder dict settings */
11195 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11196 FolderItem *folder_item)
11198 cm_return_if_fail(compose != NULL);
11200 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11201 FolderItemPrefs *prefs = folder_item->prefs;
11203 if (prefs->enable_default_dictionary)
11204 gtkaspell_change_dict(compose->gtkaspell,
11205 prefs->default_dictionary, FALSE);
11206 if (folder_item->prefs->enable_default_alt_dictionary)
11207 gtkaspell_change_alt_dict(compose->gtkaspell,
11208 prefs->default_alt_dictionary);
11209 if (prefs->enable_default_dictionary
11210 || prefs->enable_default_alt_dictionary)
11211 compose_spell_menu_changed(compose);