2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 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/>.
22 #include "claws-features.h"
27 #ifndef PANGO_ENABLE_ENGINE
28 # define PANGO_ENABLE_ENGINE
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
36 #include <pango/pango-break.h>
41 #include <sys/types.h>
47 # include <sys/wait.h>
51 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
55 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include "mainwindow.h"
64 #ifndef USE_NEW_ADDRBOOK
65 #include "addressbook.h"
67 #include "addressbook-dbus.h"
68 #include "addressadd.h"
70 #include "folderview.h"
73 #include "stock_pixmap.h"
74 #include "send_message.h"
77 #include "customheader.h"
78 #include "prefs_common.h"
79 #include "prefs_account.h"
83 #include "procheader.h"
85 #include "statusbar.h"
88 #include "quoted-printable.h"
92 #include "gtkshruler.h"
94 #include "alertpanel.h"
95 #include "manage_window.h"
97 #include "folder_item_prefs.h"
98 #include "addr_compl.h"
99 #include "quote_fmt.h"
101 #include "foldersel.h"
104 #include "message_search.h"
105 #include "combobox.h"
109 #include "autofaces.h"
110 #include "spell_entry.h"
123 #define N_ATTACH_COLS (N_COL_COLUMNS)
127 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
128 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
129 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
130 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
131 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
132 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
133 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
134 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
135 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
136 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
137 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
138 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
139 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
140 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
141 } ComposeCallAdvancedAction;
145 PRIORITY_HIGHEST = 1,
154 COMPOSE_INSERT_SUCCESS,
155 COMPOSE_INSERT_READ_ERROR,
156 COMPOSE_INSERT_INVALID_CHARACTER,
157 COMPOSE_INSERT_NO_FILE
158 } ComposeInsertResult;
162 COMPOSE_WRITE_FOR_SEND,
163 COMPOSE_WRITE_FOR_STORE
168 COMPOSE_QUOTE_FORCED,
175 SUBJECT_FIELD_PRESENT,
180 #define B64_LINE_SIZE 57
181 #define B64_BUFFSIZE 77
183 #define MAX_REFERENCES_LEN 999
185 static GList *compose_list = NULL;
186 static GSList *extra_headers = NULL;
188 static Compose *compose_generic_new (PrefsAccount *account,
192 GList *listAddress );
194 static Compose *compose_create (PrefsAccount *account,
199 static void compose_entry_mark_default_to (Compose *compose,
200 const gchar *address);
201 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
202 ComposeQuoteMode quote_mode,
206 static Compose *compose_forward_multiple (PrefsAccount *account,
207 GSList *msginfo_list);
208 static Compose *compose_reply (MsgInfo *msginfo,
209 ComposeQuoteMode quote_mode,
214 static Compose *compose_reply_mode (ComposeMode mode,
215 GSList *msginfo_list,
217 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
218 static void compose_update_privacy_systems_menu(Compose *compose);
220 static GtkWidget *compose_account_option_menu_create
222 static void compose_set_out_encoding (Compose *compose);
223 static void compose_set_template_menu (Compose *compose);
224 static void compose_destroy (Compose *compose);
226 static MailField compose_entries_set (Compose *compose,
228 ComposeEntryType to_type);
229 static gint compose_parse_header (Compose *compose,
231 static gint compose_parse_manual_headers (Compose *compose,
233 HeaderEntry *entries);
234 static gchar *compose_parse_references (const gchar *ref,
237 static gchar *compose_quote_fmt (Compose *compose,
243 gboolean need_unescape,
244 const gchar *err_msg);
246 static void compose_reply_set_entry (Compose *compose,
252 followup_and_reply_to);
253 static void compose_reedit_set_entry (Compose *compose,
256 static void compose_insert_sig (Compose *compose,
258 static ComposeInsertResult compose_insert_file (Compose *compose,
261 static gboolean compose_attach_append (Compose *compose,
264 const gchar *content_type,
265 const gchar *charset);
266 static void compose_attach_parts (Compose *compose,
269 static gboolean compose_beautify_paragraph (Compose *compose,
270 GtkTextIter *par_iter,
272 static void compose_wrap_all (Compose *compose);
273 static void compose_wrap_all_full (Compose *compose,
276 static void compose_set_title (Compose *compose);
277 static void compose_select_account (Compose *compose,
278 PrefsAccount *account,
281 static PrefsAccount *compose_current_mail_account(void);
282 /* static gint compose_send (Compose *compose); */
283 static gboolean compose_check_for_valid_recipient
285 static gboolean compose_check_entries (Compose *compose,
286 gboolean check_everything);
287 static gint compose_write_to_file (Compose *compose,
290 gboolean attach_parts);
291 static gint compose_write_body_to_file (Compose *compose,
293 static gint compose_remove_reedit_target (Compose *compose,
295 static void compose_remove_draft (Compose *compose);
296 static gint compose_queue_sub (Compose *compose,
300 gboolean check_subject,
301 gboolean remove_reedit_target);
302 static int compose_add_attachments (Compose *compose,
304 static gchar *compose_get_header (Compose *compose);
305 static gchar *compose_get_manual_headers_info (Compose *compose);
307 static void compose_convert_header (Compose *compose,
312 gboolean addr_field);
314 static void compose_attach_info_free (AttachInfo *ainfo);
315 static void compose_attach_remove_selected (GtkAction *action,
318 static void compose_template_apply (Compose *compose,
321 static void compose_attach_property (GtkAction *action,
323 static void compose_attach_property_create (gboolean *cancelled);
324 static void attach_property_ok (GtkWidget *widget,
325 gboolean *cancelled);
326 static void attach_property_cancel (GtkWidget *widget,
327 gboolean *cancelled);
328 static gint attach_property_delete_event (GtkWidget *widget,
330 gboolean *cancelled);
331 static gboolean attach_property_key_pressed (GtkWidget *widget,
333 gboolean *cancelled);
335 static void compose_exec_ext_editor (Compose *compose);
337 static gint compose_exec_ext_editor_real (const gchar *file);
338 static gboolean compose_ext_editor_kill (Compose *compose);
339 static gboolean compose_input_cb (GIOChannel *source,
340 GIOCondition condition,
342 static void compose_set_ext_editor_sensitive (Compose *compose,
344 #endif /* G_OS_UNIX */
346 static void compose_undo_state_changed (UndoMain *undostruct,
351 static void compose_create_header_entry (Compose *compose);
352 static void compose_add_header_entry (Compose *compose, const gchar *header,
353 gchar *text, ComposePrefType pref_type);
354 static void compose_remove_header_entries(Compose *compose);
356 static void compose_update_priority_menu_item(Compose * compose);
358 static void compose_spell_menu_changed (void *data);
359 static void compose_dict_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static void compose_notebook_size_alloc (GtkNotebook *notebook,
367 GtkAllocation *allocation,
369 static gboolean compose_edit_size_alloc (GtkEditable *widget,
370 GtkAllocation *allocation,
371 GtkSHRuler *shruler);
372 static void account_activated (GtkComboBox *optmenu,
374 static void attach_selected (GtkTreeView *tree_view,
375 GtkTreePath *tree_path,
376 GtkTreeViewColumn *column,
378 static gboolean attach_button_pressed (GtkWidget *widget,
379 GdkEventButton *event,
381 static gboolean attach_key_pressed (GtkWidget *widget,
384 static void compose_send_cb (GtkAction *action, gpointer data);
385 static void compose_send_later_cb (GtkAction *action, gpointer data);
387 static void compose_save_cb (GtkAction *action,
390 static void compose_attach_cb (GtkAction *action,
392 static void compose_insert_file_cb (GtkAction *action,
394 static void compose_insert_sig_cb (GtkAction *action,
397 static void compose_close_cb (GtkAction *action,
399 static void compose_print_cb (GtkAction *action,
402 static void compose_set_encoding_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
404 static void compose_address_cb (GtkAction *action,
406 static void about_show_cb (GtkAction *action,
408 static void compose_template_activate_cb(GtkWidget *widget,
411 static void compose_ext_editor_cb (GtkAction *action,
414 static gint compose_delete_cb (GtkWidget *widget,
418 static void compose_undo_cb (GtkAction *action,
420 static void compose_redo_cb (GtkAction *action,
422 static void compose_cut_cb (GtkAction *action,
424 static void compose_copy_cb (GtkAction *action,
426 static void compose_paste_cb (GtkAction *action,
428 static void compose_paste_as_quote_cb (GtkAction *action,
430 static void compose_paste_no_wrap_cb (GtkAction *action,
432 static void compose_paste_wrap_cb (GtkAction *action,
434 static void compose_allsel_cb (GtkAction *action,
437 static void compose_advanced_action_cb (GtkAction *action,
440 static void compose_grab_focus_cb (GtkWidget *widget,
443 static void compose_changed_cb (GtkTextBuffer *textbuf,
446 static void compose_wrap_cb (GtkAction *action,
448 static void compose_wrap_all_cb (GtkAction *action,
450 static void compose_find_cb (GtkAction *action,
452 static void compose_toggle_autowrap_cb (GtkToggleAction *action,
454 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
457 static void compose_toggle_ruler_cb (GtkToggleAction *action,
459 static void compose_toggle_sign_cb (GtkToggleAction *action,
461 static void compose_toggle_encrypt_cb (GtkToggleAction *action,
463 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data);
464 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn);
465 static void activate_privacy_system (Compose *compose,
466 PrefsAccount *account,
468 static void compose_use_signing(Compose *compose, gboolean use_signing);
469 static void compose_use_encryption(Compose *compose, gboolean use_encryption);
470 static void compose_toggle_return_receipt_cb(GtkToggleAction *action,
472 static void compose_toggle_remove_refs_cb(GtkToggleAction *action,
474 static void compose_set_priority_cb (GtkAction *action, GtkRadioAction *current, gpointer data);
475 static void compose_reply_change_mode (Compose *compose, ComposeMode action);
476 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
478 static void compose_attach_drag_received_cb (GtkWidget *widget,
479 GdkDragContext *drag_context,
482 GtkSelectionData *data,
486 static void compose_insert_drag_received_cb (GtkWidget *widget,
487 GdkDragContext *drag_context,
490 GtkSelectionData *data,
494 static void compose_header_drag_received_cb (GtkWidget *widget,
495 GdkDragContext *drag_context,
498 GtkSelectionData *data,
503 static gboolean compose_drag_drop (GtkWidget *widget,
504 GdkDragContext *drag_context,
506 guint time, gpointer user_data);
507 static gboolean completion_set_focus_to_subject
512 static void text_inserted (GtkTextBuffer *buffer,
517 static Compose *compose_generic_reply(MsgInfo *msginfo,
518 ComposeQuoteMode quote_mode,
522 gboolean followup_and_reply_to,
525 static void compose_headerentry_changed_cb (GtkWidget *entry,
526 ComposeHeaderEntry *headerentry);
527 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
529 ComposeHeaderEntry *headerentry);
530 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
531 ComposeHeaderEntry *headerentry);
533 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
535 static void compose_allow_user_actions (Compose *compose, gboolean allow);
537 static void compose_nothing_cb (GtkAction *action, gpointer data)
543 static void compose_check_all (GtkAction *action, gpointer data);
544 static void compose_highlight_all (GtkAction *action, gpointer data);
545 static void compose_check_backwards (GtkAction *action, gpointer data);
546 static void compose_check_forwards_go (GtkAction *action, gpointer data);
549 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
551 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
554 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
555 FolderItem *folder_item);
557 static void compose_attach_update_label(Compose *compose);
558 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
559 gboolean respect_default_to);
561 static GtkActionEntry compose_popup_entries[] =
563 {"Compose", NULL, "Compose" },
564 {"Compose/Add", NULL, N_("_Add..."), NULL, NULL, G_CALLBACK(compose_attach_cb) },
565 {"Compose/Remove", NULL, N_("_Remove"), NULL, NULL, G_CALLBACK(compose_attach_remove_selected) },
566 {"Compose/---", NULL, "---", NULL, NULL, NULL },
567 {"Compose/Properties", NULL, N_("_Properties..."), NULL, NULL, G_CALLBACK(compose_attach_property) },
570 static GtkActionEntry compose_entries[] =
572 {"Menu", NULL, "Menu" },
574 {"Message", NULL, N_("_Message") },
575 {"Edit", NULL, N_("_Edit") },
577 {"Spelling", NULL, N_("_Spelling") },
579 {"Options", NULL, N_("_Options") },
580 {"Tools", NULL, N_("_Tools") },
581 {"Help", NULL, N_("_Help") },
583 {"Message/Send", NULL, N_("S_end"), "<control>Return", NULL, G_CALLBACK(compose_send_cb) },
584 {"Message/SendLater", NULL, N_("Send _later"), "<shift><control>S", NULL, G_CALLBACK(compose_send_later_cb) },
585 {"Message/---", NULL, "---" },
587 {"Message/AttachFile", NULL, N_("_Attach file"), "<control>M", NULL, G_CALLBACK(compose_attach_cb) },
588 {"Message/InsertFile", NULL, N_("_Insert file"), "<control>I", NULL, G_CALLBACK(compose_insert_file_cb) },
589 {"Message/InsertSig", NULL, N_("Insert si_gnature"), "<control>G", NULL, G_CALLBACK(compose_insert_sig_cb) },
590 /* {"Message/---", NULL, "---" }, */
591 {"Message/Save", NULL, N_("_Save"), "<control>S", NULL, G_CALLBACK(compose_save_cb) }, /*COMPOSE_KEEP_EDITING*/
592 /* {"Message/---", NULL, "---" }, */
593 {"Message/Print", NULL, N_("_Print"), NULL, NULL, G_CALLBACK(compose_print_cb) },
594 /* {"Message/---", NULL, "---" }, */
595 {"Message/Close", NULL, N_("_Close"), "<control>W", NULL, G_CALLBACK(compose_close_cb) },
598 {"Edit/Undo", NULL, N_("_Undo"), "<control>Z", NULL, G_CALLBACK(compose_undo_cb) },
599 {"Edit/Redo", NULL, N_("_Redo"), "<control>Y", NULL, G_CALLBACK(compose_redo_cb) },
600 {"Edit/---", NULL, "---" },
602 {"Edit/Cut", NULL, N_("Cu_t"), "<control>X", NULL, G_CALLBACK(compose_cut_cb) },
603 {"Edit/Copy", NULL, N_("_Copy"), "<control>C", NULL, G_CALLBACK(compose_copy_cb) },
604 {"Edit/Paste", NULL, N_("_Paste"), "<control>V", NULL, G_CALLBACK(compose_paste_cb) },
606 {"Edit/SpecialPaste", NULL, N_("_Special paste") },
607 {"Edit/SpecialPaste/AsQuotation", NULL, N_("As _quotation"), NULL, NULL, G_CALLBACK(compose_paste_as_quote_cb) },
608 {"Edit/SpecialPaste/Wrapped", NULL, N_("_Wrapped"), NULL, NULL, G_CALLBACK(compose_paste_wrap_cb) },
609 {"Edit/SpecialPaste/Unwrapped", NULL, N_("_Unwrapped"), NULL, NULL, G_CALLBACK(compose_paste_no_wrap_cb) },
611 {"Edit/SelectAll", NULL, N_("Select _all"), "<control>A", NULL, G_CALLBACK(compose_allsel_cb) },
613 {"Edit/Advanced", NULL, N_("A_dvanced") },
614 {"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*/
615 {"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*/
616 {"Edit/Advanced/BackWord", NULL, N_("Move a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD*/
617 {"Edit/Advanced/ForwWord", NULL, N_("Move a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD*/
618 {"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*/
619 {"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*/
620 {"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*/
621 {"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*/
622 {"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*/
623 {"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*/
624 {"Edit/Advanced/DelBackWord", NULL, N_("Delete a word backward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD*/
625 {"Edit/Advanced/DelForwWord", NULL, N_("Delete a word forward"), NULL, NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD*/
626 {"Edit/Advanced/DelLine", NULL, N_("Delete line"), "<control>U", NULL, G_CALLBACK(compose_advanced_action_cb) }, /*COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE*/
627 {"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*/
629 /* {"Edit/---", NULL, "---" }, */
630 {"Edit/Find", NULL, N_("_Find"), "<control>F", NULL, G_CALLBACK(compose_find_cb) },
632 /* {"Edit/---", NULL, "---" }, */
633 {"Edit/WrapPara", NULL, N_("_Wrap current paragraph"), "<control>L", NULL, G_CALLBACK(compose_wrap_cb) }, /* 0 */
634 {"Edit/WrapAllLines", NULL, N_("Wrap all long _lines"), "<control><alt>L", NULL, G_CALLBACK(compose_wrap_all_cb) }, /* 1 */
635 /* {"Edit/---", NULL, "---" }, */
636 {"Edit/ExtEditor", NULL, N_("Edit with e_xternal editor"), "<shift><control>X", NULL, G_CALLBACK(compose_ext_editor_cb) },
639 {"Spelling/CheckAllSel", NULL, N_("_Check all or check selection"), NULL, NULL, G_CALLBACK(compose_check_all) },
640 {"Spelling/HighlightAll", NULL, N_("_Highlight all misspelled words"), NULL, NULL, G_CALLBACK(compose_highlight_all) },
641 {"Spelling/CheckBackwards", NULL, N_("Check _backwards misspelled word"), NULL, NULL, G_CALLBACK(compose_check_backwards) },
642 {"Spelling/ForwardNext", NULL, N_("_Forward to next misspelled word"), NULL, NULL, G_CALLBACK(compose_check_forwards_go) },
644 {"Spelling/---", NULL, "---" },
645 {"Spelling/Options", NULL, N_("_Options") },
650 {"Options/ReplyMode", NULL, N_("Reply _mode") },
651 {"Options/---", NULL, "---" },
652 {"Options/PrivacySystem", NULL, N_("Privacy _System") },
653 {"Options/PrivacySystem/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
655 /* {"Options/---", NULL, "---" }, */
657 {"Options/Priority", NULL, N_("_Priority") },
659 {"Options/Encoding", NULL, N_("Character _encoding") },
660 {"Options/Encoding/---", NULL, "---" },
661 #define ENC_ACTION(cs_char,c_char,string) \
662 { "Options/Encoding/" cs_char, NULL, N_(string), NULL, NULL, c_char }
664 {"Options/Encoding/Western", NULL, N_("Western European") },
665 {"Options/Encoding/Baltic", NULL, N_("Baltic") },
666 {"Options/Encoding/Hebrew", NULL, N_("Hebrew") },
667 {"Options/Encoding/Arabic", NULL, N_("Arabic") },
668 {"Options/Encoding/Cyrillic", NULL, N_("Cyrillic") },
669 {"Options/Encoding/Japanese", NULL, N_("Japanese") },
670 {"Options/Encoding/Chinese", NULL, N_("Chinese") },
671 {"Options/Encoding/Korean", NULL, N_("Korean") },
672 {"Options/Encoding/Thai", NULL, N_("Thai") },
675 {"Tools/AddressBook", NULL, N_("_Address book"), NULL, NULL, G_CALLBACK(compose_address_cb) },
677 {"Tools/Template", NULL, N_("_Template") },
678 {"Tools/Template/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
679 {"Tools/Actions", NULL, N_("Actio_ns") },
680 {"Tools/Actions/PlaceHolder", NULL, "Placeholder", NULL, NULL, G_CALLBACK(compose_nothing_cb) },
683 {"Help/About", NULL, N_("_About"), NULL, NULL, G_CALLBACK(about_show_cb) },
686 static GtkToggleActionEntry compose_toggle_entries[] =
688 {"Edit/AutoWrap", NULL, N_("Aut_o wrapping"), "<shift><control>L", NULL, G_CALLBACK(compose_toggle_autowrap_cb) }, /* TOGGLE */
689 {"Edit/AutoIndent", NULL, N_("Auto _indent"), NULL, NULL, G_CALLBACK(compose_toggle_autoindent_cb) }, /* TOGGLE */
690 {"Options/Sign", NULL, N_("Si_gn"), NULL, NULL, G_CALLBACK(compose_toggle_sign_cb) }, /* Toggle */
691 {"Options/Encrypt", NULL, N_("_Encrypt"), NULL, NULL, G_CALLBACK(compose_toggle_encrypt_cb) }, /* Toggle */
692 {"Options/RequestRetRcpt", NULL, N_("_Request Return Receipt"), NULL, NULL, G_CALLBACK(compose_toggle_return_receipt_cb) }, /* TOGGLE */
693 {"Options/RemoveReferences", NULL, N_("Remo_ve references"), NULL, NULL, G_CALLBACK(compose_toggle_remove_refs_cb) }, /* TOGGLE */
694 {"Tools/ShowRuler", NULL, N_("Show _ruler"), NULL, NULL, G_CALLBACK(compose_toggle_ruler_cb) }, /* Toggle */
697 static GtkRadioActionEntry compose_radio_rm_entries[] =
699 {"Options/ReplyMode/Normal", NULL, N_("_Normal"), NULL, NULL, COMPOSE_REPLY }, /* RADIO compose_reply_change_mode_cb */
700 {"Options/ReplyMode/All", NULL, N_("_All"), NULL, NULL, COMPOSE_REPLY_TO_ALL }, /* RADIO compose_reply_change_mode_cb */
701 {"Options/ReplyMode/Sender", NULL, N_("_Sender"), NULL, NULL, COMPOSE_REPLY_TO_SENDER }, /* RADIO compose_reply_change_mode_cb */
702 {"Options/ReplyMode/List", NULL, N_("_Mailing-list"), NULL, NULL, COMPOSE_REPLY_TO_LIST }, /* RADIO compose_reply_change_mode_cb */
705 static GtkRadioActionEntry compose_radio_prio_entries[] =
707 {"Options/Priority/Highest", NULL, N_("_Highest"), NULL, NULL, PRIORITY_HIGHEST }, /* RADIO compose_set_priority_cb */
708 {"Options/Priority/High", NULL, N_("Hi_gh"), NULL, NULL, PRIORITY_HIGH }, /* RADIO compose_set_priority_cb */
709 {"Options/Priority/Normal", NULL, N_("_Normal"), NULL, NULL, PRIORITY_NORMAL }, /* RADIO compose_set_priority_cb */
710 {"Options/Priority/Low", NULL, N_("Lo_w"), NULL, NULL, PRIORITY_LOW }, /* RADIO compose_set_priority_cb */
711 {"Options/Priority/Lowest", NULL, N_("_Lowest"), NULL, NULL, PRIORITY_LOWEST }, /* RADIO compose_set_priority_cb */
714 static GtkRadioActionEntry compose_radio_enc_entries[] =
716 ENC_ACTION(CS_AUTO, C_AUTO, N_("_Automatic")), /* RADIO compose_set_encoding_cb */
717 ENC_ACTION(CS_US_ASCII, C_US_ASCII, N_("7bit ASCII (US-ASC_II)")), /* RADIO compose_set_encoding_cb */
718 ENC_ACTION(CS_UTF_8, C_UTF_8, N_("Unicode (_UTF-8)")), /* RADIO compose_set_encoding_cb */
719 ENC_ACTION("Western/"CS_ISO_8859_1, C_ISO_8859_1, "ISO-8859-_1"), /* RADIO compose_set_encoding_cb */
720 ENC_ACTION("Western/"CS_ISO_8859_15, C_ISO_8859_15, "ISO-8859-15"), /* RADIO compose_set_encoding_cb */
721 ENC_ACTION("Western/"CS_WINDOWS_1252, C_WINDOWS_1252, "Windows-1252"), /* RADIO compose_set_encoding_cb */
722 ENC_ACTION(CS_ISO_8859_2, C_ISO_8859_2, N_("Central European (ISO-8859-_2)")), /* RADIO compose_set_encoding_cb */
723 ENC_ACTION("Baltic/"CS_ISO_8859_13, C_ISO_8859_13, "ISO-8859-13"), /* RADIO compose_set_encoding_cb */
724 ENC_ACTION("Baltic/"CS_ISO_8859_4, C_ISO_8859_14, "ISO-8859-_4"), /* RADIO compose_set_encoding_cb */
725 ENC_ACTION(CS_ISO_8859_7, C_ISO_8859_7, N_("Greek (ISO-8859-_7)")), /* RADIO compose_set_encoding_cb */
726 ENC_ACTION("Hebrew/"CS_ISO_8859_8, C_ISO_8859_8, "ISO-8859-_8"), /* RADIO compose_set_encoding_cb */
727 ENC_ACTION("Hebrew/"CS_WINDOWS_1255, C_WINDOWS_1255, "Windows-1255"), /* RADIO compose_set_encoding_cb */
728 ENC_ACTION("Arabic/"CS_ISO_8859_6, C_ISO_8859_6, "ISO-8859-_6"), /* RADIO compose_set_encoding_cb */
729 ENC_ACTION("Arabic/"CS_WINDOWS_1256, C_WINDOWS_1256, "Windows-1256"), /* RADIO compose_set_encoding_cb */
730 ENC_ACTION(CS_ISO_8859_9, C_ISO_8859_9, N_("Turkish (ISO-8859-_9)")), /* RADIO compose_set_encoding_cb */
731 ENC_ACTION("Cyrillic/"CS_ISO_8859_5, C_ISO_8859_5, "ISO-8859-_5"), /* RADIO compose_set_encoding_cb */
732 ENC_ACTION("Cyrillic/"CS_KOI8_R, C_KOI8_R, "KOI8-_R"), /* RADIO compose_set_encoding_cb */
733 ENC_ACTION("Cyrillic/"CS_KOI8_U, C_KOI8_U, "KOI8-_U"), /* RADIO compose_set_encoding_cb */
734 ENC_ACTION("Cyrillic/"CS_WINDOWS_1251, C_WINDOWS_1251, "Windows-1251"), /* RADIO compose_set_encoding_cb */
735 ENC_ACTION("Japanese/"CS_ISO_2022_JP, C_ISO_2022_JP, "ISO-2022-_JP"), /* RADIO compose_set_encoding_cb */
736 ENC_ACTION("Japanese/"CS_ISO_2022_JP_2, C_ISO_2022_JP_2, "ISO-2022-JP-_2"), /* RADIO compose_set_encoding_cb */
737 ENC_ACTION("Japanese/"CS_EUC_JP, C_EUC_JP, "_EUC-JP"), /* RADIO compose_set_encoding_cb */
738 ENC_ACTION("Japanese/"CS_SHIFT_JIS, C_SHIFT_JIS, "_Shift-JIS"), /* RADIO compose_set_encoding_cb */
739 ENC_ACTION("Chinese/"CS_GB18030, C_GB18030, "_GB18030"), /* RADIO compose_set_encoding_cb */
740 ENC_ACTION("Chinese/"CS_GB2312, C_GB2312, "_GB2312"), /* RADIO compose_set_encoding_cb */
741 ENC_ACTION("Chinese/"CS_GBK, C_GBK, "GB_K"), /* RADIO compose_set_encoding_cb */
742 ENC_ACTION("Chinese/"CS_BIG5, C_BIG5, "_Big5-JP"), /* RADIO compose_set_encoding_cb */
743 ENC_ACTION("Chinese/"CS_EUC_TW, C_EUC_TW, "EUC-_TW"), /* RADIO compose_set_encoding_cb */
744 ENC_ACTION("Korean/"CS_EUC_KR, C_EUC_KR, "_EUC-KR"), /* RADIO compose_set_encoding_cb */
745 ENC_ACTION("Korean/"CS_ISO_2022_KR, C_ISO_2022_KR, "_ISO-2022-KR"), /* RADIO compose_set_encoding_cb */
746 ENC_ACTION("Thai/"CS_TIS_620, C_TIS_620, "_TIS-620-KR"), /* RADIO compose_set_encoding_cb */
747 ENC_ACTION("Thai/"CS_WINDOWS_874, C_WINDOWS_874, "_Windows-874"), /* RADIO compose_set_encoding_cb */
750 static GtkTargetEntry compose_mime_types[] =
752 {"text/uri-list", 0, 0},
753 {"UTF8_STRING", 0, 0},
757 static gboolean compose_put_existing_to_front(MsgInfo *info)
759 GList *compose_list = compose_get_compose_list();
763 for (elem = compose_list; elem != NULL && elem->data != NULL;
765 Compose *c = (Compose*)elem->data;
767 if (!c->targetinfo || !c->targetinfo->msgid ||
771 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
772 gtkut_window_popup(c->window);
780 static GdkColor quote_color1 =
781 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
782 static GdkColor quote_color2 =
783 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
784 static GdkColor quote_color3 =
785 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
787 static GdkColor quote_bgcolor1 =
788 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
789 static GdkColor quote_bgcolor2 =
790 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
791 static GdkColor quote_bgcolor3 =
792 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
794 static GdkColor signature_color = {
801 static GdkColor uri_color = {
808 static void compose_create_tags(GtkTextView *text, Compose *compose)
810 GtkTextBuffer *buffer;
811 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
812 #if !GTK_CHECK_VERSION(2, 24, 0)
819 buffer = gtk_text_view_get_buffer(text);
821 if (prefs_common.enable_color) {
822 /* grab the quote colors, converting from an int to a GdkColor */
823 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
825 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
827 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
829 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
831 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
833 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
835 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
837 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
840 signature_color = quote_color1 = quote_color2 = quote_color3 =
841 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
844 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
845 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
846 "foreground-gdk", "e_color1,
847 "paragraph-background-gdk", "e_bgcolor1,
849 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
850 "foreground-gdk", "e_color2,
851 "paragraph-background-gdk", "e_bgcolor2,
853 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
854 "foreground-gdk", "e_color3,
855 "paragraph-background-gdk", "e_bgcolor3,
858 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
859 "foreground-gdk", "e_color1,
861 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
862 "foreground-gdk", "e_color2,
864 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
865 "foreground-gdk", "e_color3,
869 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
870 "foreground-gdk", &signature_color,
873 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
874 "foreground-gdk", &uri_color,
876 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
877 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
879 #if !GTK_CHECK_VERSION(2, 24, 0)
880 color[0] = quote_color1;
881 color[1] = quote_color2;
882 color[2] = quote_color3;
883 color[3] = quote_bgcolor1;
884 color[4] = quote_bgcolor2;
885 color[5] = quote_bgcolor3;
886 color[6] = signature_color;
887 color[7] = uri_color;
889 cmap = gdk_drawable_get_colormap(gtk_widget_get_window(compose->window));
890 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
892 for (i = 0; i < 8; i++) {
893 if (success[i] == FALSE) {
894 g_warning("Compose: color allocation failed.\n");
895 quote_color1 = quote_color2 = quote_color3 =
896 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
897 signature_color = uri_color = black;
903 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
906 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
909 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
911 return compose_generic_new(account, mailto, item, NULL, NULL);
914 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
916 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
919 #define SCROLL_TO_CURSOR(compose) { \
920 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
921 gtk_text_view_get_buffer( \
922 GTK_TEXT_VIEW(compose->text))); \
923 gtk_text_view_scroll_mark_onscreen( \
924 GTK_TEXT_VIEW(compose->text), \
928 static void compose_set_save_to(Compose *compose, const gchar *folderidentifier)
931 if (folderidentifier) {
932 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
933 prefs_common.compose_save_to_history = add_history(
934 prefs_common.compose_save_to_history, folderidentifier);
935 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
936 prefs_common.compose_save_to_history);
939 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
940 if (folderidentifier)
941 gtk_entry_set_text(GTK_ENTRY(entry), folderidentifier);
943 gtk_entry_set_text(GTK_ENTRY(entry), "");
946 static gchar *compose_get_save_to(Compose *compose)
949 gchar *result = NULL;
950 entry = GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(compose->savemsg_combo)));
951 result = gtk_editable_get_chars(entry, 0, -1);
954 combobox_unset_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo));
955 prefs_common.compose_save_to_history = add_history(
956 prefs_common.compose_save_to_history, result);
957 combobox_set_popdown_strings(GTK_COMBO_BOX(compose->savemsg_combo),
958 prefs_common.compose_save_to_history);
963 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
964 GList *attach_files, GList *listAddress )
967 GtkTextView *textview;
968 GtkTextBuffer *textbuf;
970 const gchar *subject_format = NULL;
971 const gchar *body_format = NULL;
972 gchar *mailto_from = NULL;
973 PrefsAccount *mailto_account = NULL;
974 MsgInfo* dummyinfo = NULL;
975 gint cursor_pos = -1;
976 MailField mfield = NO_FIELD_PRESENT;
980 /* check if mailto defines a from */
981 if (mailto && *mailto != '\0') {
982 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
983 /* mailto defines a from, check if we can get account prefs from it,
984 if not, the account prefs will be guessed using other ways, but we'll keep
987 mailto_account = account_find_from_address(mailto_from, TRUE);
989 account = mailto_account;
992 /* if no account prefs set from mailto, set if from folder prefs (if any) */
993 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
994 account = account_find_from_id(item->prefs->default_account);
996 /* if no account prefs set, fallback to the current one */
997 if (!account) account = cur_account;
998 cm_return_val_if_fail(account != NULL, NULL);
1000 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1002 /* override from name if mailto asked for it */
1004 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1005 g_free(mailto_from);
1007 /* override from name according to folder properties */
1008 if (item && item->prefs &&
1009 item->prefs->compose_with_format &&
1010 item->prefs->compose_override_from_format &&
1011 *item->prefs->compose_override_from_format != '\0') {
1016 dummyinfo = compose_msginfo_new_from_compose(compose);
1018 /* decode \-escape sequences in the internal representation of the quote format */
1019 tmp = g_malloc(strlen(item->prefs->compose_override_from_format)+1);
1020 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1023 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1024 compose->gtkaspell);
1026 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1028 quote_fmt_scan_string(tmp);
1031 buf = quote_fmt_get_buffer();
1033 alertpanel_error(_("New message From format error."));
1035 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1036 quote_fmt_reset_vartable();
1041 compose->replyinfo = NULL;
1042 compose->fwdinfo = NULL;
1044 textview = GTK_TEXT_VIEW(compose->text);
1045 textbuf = gtk_text_view_get_buffer(textview);
1046 compose_create_tags(textview, compose);
1048 undo_block(compose->undostruct);
1050 compose_set_dictionaries_from_folder_prefs(compose, item);
1053 if (account->auto_sig)
1054 compose_insert_sig(compose, FALSE);
1055 gtk_text_buffer_get_start_iter(textbuf, &iter);
1056 gtk_text_buffer_place_cursor(textbuf, &iter);
1058 if (account->protocol != A_NNTP) {
1059 if (mailto && *mailto != '\0') {
1060 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1063 compose_set_folder_prefs(compose, item, TRUE);
1065 if (item && item->ret_rcpt) {
1066 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1069 if (mailto && *mailto != '\0') {
1070 if (!strchr(mailto, '@'))
1071 mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1073 mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
1074 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1075 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
1076 mfield = TO_FIELD_PRESENT;
1079 * CLAWS: just don't allow return receipt request, even if the user
1080 * may want to send an email. simple but foolproof.
1082 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", FALSE);
1084 compose_add_field_list( compose, listAddress );
1086 if (item && item->prefs && item->prefs->compose_with_format) {
1087 subject_format = item->prefs->compose_subject_format;
1088 body_format = item->prefs->compose_body_format;
1089 } else if (account->compose_with_format) {
1090 subject_format = account->compose_subject_format;
1091 body_format = account->compose_body_format;
1092 } else if (prefs_common.compose_with_format) {
1093 subject_format = prefs_common.compose_subject_format;
1094 body_format = prefs_common.compose_body_format;
1097 if (subject_format || body_format) {
1100 && *subject_format != '\0' )
1102 gchar *subject = NULL;
1107 dummyinfo = compose_msginfo_new_from_compose(compose);
1109 /* decode \-escape sequences in the internal representation of the quote format */
1110 tmp = g_malloc(strlen(subject_format)+1);
1111 pref_get_unescaped_pref(tmp, subject_format);
1113 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1115 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1116 compose->gtkaspell);
1118 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1120 quote_fmt_scan_string(tmp);
1123 buf = quote_fmt_get_buffer();
1125 alertpanel_error(_("New message subject format error."));
1127 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1128 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1129 quote_fmt_reset_vartable();
1133 mfield = SUBJECT_FIELD_PRESENT;
1137 && *body_format != '\0' )
1140 GtkTextBuffer *buffer;
1141 GtkTextIter start, end;
1145 dummyinfo = compose_msginfo_new_from_compose(compose);
1147 text = GTK_TEXT_VIEW(compose->text);
1148 buffer = gtk_text_view_get_buffer(text);
1149 gtk_text_buffer_get_start_iter(buffer, &start);
1150 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1151 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1153 compose_quote_fmt(compose, dummyinfo,
1155 NULL, tmp, FALSE, TRUE,
1156 _("The body of the \"New message\" template has an error at line %d."));
1157 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1158 quote_fmt_reset_vartable();
1162 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1163 gtkaspell_highlight_all(compose->gtkaspell);
1165 mfield = BODY_FIELD_PRESENT;
1169 procmsg_msginfo_free( dummyinfo );
1175 for (curr = attach_files ; curr != NULL ; curr = curr->next) {
1176 ainfo = (AttachInfo *) curr->data;
1177 compose_attach_append(compose, ainfo->file, ainfo->name,
1178 ainfo->content_type, ainfo->charset);
1182 compose_show_first_last_header(compose, TRUE);
1184 /* Set save folder */
1185 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1186 gchar *folderidentifier;
1188 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1189 folderidentifier = folder_item_get_identifier(item);
1190 compose_set_save_to(compose, folderidentifier);
1191 g_free(folderidentifier);
1194 /* Place cursor according to provided input (mfield) */
1196 case NO_FIELD_PRESENT:
1197 if (compose->header_last)
1198 gtk_widget_grab_focus(compose->header_last->entry);
1200 case TO_FIELD_PRESENT:
1201 buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1203 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1206 gtk_widget_grab_focus(compose->subject_entry);
1208 case SUBJECT_FIELD_PRESENT:
1209 textview = GTK_TEXT_VIEW(compose->text);
1212 textbuf = gtk_text_view_get_buffer(textview);
1215 mark = gtk_text_buffer_get_insert(textbuf);
1216 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
1217 gtk_text_buffer_insert(textbuf, &iter, "", -1);
1219 * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
1220 * only defers where it comes to the variable body
1221 * is not null. If no body is present compose->text
1222 * will be null in which case you cannot place the
1223 * cursor inside the component so. An empty component
1224 * is therefore created before placing the cursor
1226 case BODY_FIELD_PRESENT:
1227 cursor_pos = quote_fmt_get_cursor_pos();
1228 if (cursor_pos == -1)
1229 gtk_widget_grab_focus(compose->header_last->entry);
1231 gtk_widget_grab_focus(compose->text);
1235 undo_unblock(compose->undostruct);
1237 if (prefs_common.auto_exteditor)
1238 compose_exec_ext_editor(compose);
1240 compose->draft_timeout_tag = -1;
1241 SCROLL_TO_CURSOR(compose);
1243 compose->modified = FALSE;
1244 compose_set_title(compose);
1246 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1251 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1252 gboolean override_pref, const gchar *system)
1254 const gchar *privacy = NULL;
1256 cm_return_if_fail(compose != NULL);
1257 cm_return_if_fail(account != NULL);
1259 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1264 else if (account->default_privacy_system
1265 && strlen(account->default_privacy_system)) {
1266 privacy = account->default_privacy_system;
1268 GSList *privacy_avail = privacy_get_system_ids();
1269 if (privacy_avail && g_slist_length(privacy_avail)) {
1270 privacy = (gchar *)(privacy_avail->data);
1273 if (privacy != NULL) {
1275 g_free(compose->privacy_system);
1276 compose->privacy_system = NULL;
1278 if (compose->privacy_system == NULL)
1279 compose->privacy_system = g_strdup(privacy);
1280 else if (*(compose->privacy_system) == '\0') {
1281 g_free(compose->privacy_system);
1282 compose->privacy_system = g_strdup(privacy);
1284 compose_update_privacy_system_menu_item(compose, FALSE);
1285 compose_use_encryption(compose, TRUE);
1289 static void compose_force_signing(Compose *compose, PrefsAccount *account, const gchar *system)
1291 const gchar *privacy = NULL;
1295 else if (account->default_privacy_system
1296 && strlen(account->default_privacy_system)) {
1297 privacy = account->default_privacy_system;
1299 GSList *privacy_avail = privacy_get_system_ids();
1300 if (privacy_avail && g_slist_length(privacy_avail)) {
1301 privacy = (gchar *)(privacy_avail->data);
1305 if (privacy != NULL) {
1307 g_free(compose->privacy_system);
1308 compose->privacy_system = NULL;
1310 if (compose->privacy_system == NULL)
1311 compose->privacy_system = g_strdup(privacy);
1312 compose_update_privacy_system_menu_item(compose, FALSE);
1313 compose_use_signing(compose, TRUE);
1317 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1321 Compose *compose = NULL;
1323 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1325 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1326 cm_return_val_if_fail(msginfo != NULL, NULL);
1328 list_len = g_slist_length(msginfo_list);
1332 case COMPOSE_REPLY_TO_ADDRESS:
1333 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1334 FALSE, prefs_common.default_reply_list, FALSE, body);
1336 case COMPOSE_REPLY_WITH_QUOTE:
1337 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1338 FALSE, prefs_common.default_reply_list, FALSE, body);
1340 case COMPOSE_REPLY_WITHOUT_QUOTE:
1341 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1342 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1344 case COMPOSE_REPLY_TO_SENDER:
1345 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1346 FALSE, FALSE, TRUE, body);
1348 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1349 compose = compose_followup_and_reply_to(msginfo,
1350 COMPOSE_QUOTE_CHECK,
1351 FALSE, FALSE, body);
1353 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1354 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1355 FALSE, FALSE, TRUE, body);
1357 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1358 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1359 FALSE, FALSE, TRUE, NULL);
1361 case COMPOSE_REPLY_TO_ALL:
1362 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1363 TRUE, FALSE, FALSE, body);
1365 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1366 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1367 TRUE, FALSE, FALSE, body);
1369 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1370 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1371 TRUE, FALSE, FALSE, NULL);
1373 case COMPOSE_REPLY_TO_LIST:
1374 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1375 FALSE, TRUE, FALSE, body);
1377 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1378 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1379 FALSE, TRUE, FALSE, body);
1381 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1382 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1383 FALSE, TRUE, FALSE, NULL);
1385 case COMPOSE_FORWARD:
1386 if (prefs_common.forward_as_attachment) {
1387 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1390 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1394 case COMPOSE_FORWARD_INLINE:
1395 /* check if we reply to more than one Message */
1396 if (list_len == 1) {
1397 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1400 /* more messages FALL THROUGH */
1401 case COMPOSE_FORWARD_AS_ATTACH:
1402 compose = compose_forward_multiple(NULL, msginfo_list);
1404 case COMPOSE_REDIRECT:
1405 compose = compose_redirect(NULL, msginfo, FALSE);
1408 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1411 if (compose == NULL) {
1412 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1416 compose->rmode = mode;
1417 switch (compose->rmode) {
1419 case COMPOSE_REPLY_WITH_QUOTE:
1420 case COMPOSE_REPLY_WITHOUT_QUOTE:
1421 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1422 debug_print("reply mode Normal\n");
1423 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Normal", TRUE);
1424 compose_reply_change_mode(compose, COMPOSE_REPLY); /* force update */
1426 case COMPOSE_REPLY_TO_SENDER:
1427 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1428 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1429 debug_print("reply mode Sender\n");
1430 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/Sender", TRUE);
1432 case COMPOSE_REPLY_TO_ALL:
1433 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1434 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1435 debug_print("reply mode All\n");
1436 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/All", TRUE);
1438 case COMPOSE_REPLY_TO_LIST:
1439 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1440 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1441 debug_print("reply mode List\n");
1442 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/ReplyMode/List", TRUE);
1444 case COMPOSE_REPLY_TO_ADDRESS:
1445 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", FALSE);
1453 static Compose *compose_reply(MsgInfo *msginfo,
1454 ComposeQuoteMode quote_mode,
1460 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1461 to_sender, FALSE, body);
1464 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1465 ComposeQuoteMode quote_mode,
1470 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1471 to_sender, TRUE, body);
1474 static void compose_extract_original_charset(Compose *compose)
1476 MsgInfo *info = NULL;
1477 if (compose->replyinfo) {
1478 info = compose->replyinfo;
1479 } else if (compose->fwdinfo) {
1480 info = compose->fwdinfo;
1481 } else if (compose->targetinfo) {
1482 info = compose->targetinfo;
1485 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1486 MimeInfo *partinfo = mimeinfo;
1487 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1488 partinfo = procmime_mimeinfo_next(partinfo);
1490 compose->orig_charset =
1491 g_strdup(procmime_mimeinfo_get_parameter(
1492 partinfo, "charset"));
1494 procmime_mimeinfo_free_all(mimeinfo);
1498 #define SIGNAL_BLOCK(buffer) { \
1499 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1500 G_CALLBACK(compose_changed_cb), \
1502 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1503 G_CALLBACK(text_inserted), \
1507 #define SIGNAL_UNBLOCK(buffer) { \
1508 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1509 G_CALLBACK(compose_changed_cb), \
1511 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1512 G_CALLBACK(text_inserted), \
1516 static Compose *compose_generic_reply(MsgInfo *msginfo,
1517 ComposeQuoteMode quote_mode,
1518 gboolean to_all, gboolean to_ml,
1520 gboolean followup_and_reply_to,
1524 PrefsAccount *account = NULL;
1525 GtkTextView *textview;
1526 GtkTextBuffer *textbuf;
1527 gboolean quote = FALSE;
1528 const gchar *qmark = NULL;
1529 const gchar *body_fmt = NULL;
1530 gchar *s_system = NULL;
1532 cm_return_val_if_fail(msginfo != NULL, NULL);
1533 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1535 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1537 cm_return_val_if_fail(account != NULL, NULL);
1539 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1541 compose->updating = TRUE;
1543 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
1544 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", TRUE);
1546 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1547 if (!compose->replyinfo)
1548 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1550 compose_extract_original_charset(compose);
1552 if (msginfo->folder && msginfo->folder->ret_rcpt)
1553 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
1555 /* Set save folder */
1556 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1557 gchar *folderidentifier;
1559 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1560 folderidentifier = folder_item_get_identifier(msginfo->folder);
1561 compose_set_save_to(compose, folderidentifier);
1562 g_free(folderidentifier);
1565 if (compose_parse_header(compose, msginfo) < 0) {
1566 compose->updating = FALSE;
1567 compose_destroy(compose);
1571 /* override from name according to folder properties */
1572 if (msginfo->folder && msginfo->folder->prefs &&
1573 msginfo->folder->prefs->reply_with_format &&
1574 msginfo->folder->prefs->reply_override_from_format &&
1575 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1580 /* decode \-escape sequences in the internal representation of the quote format */
1581 tmp = g_malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1582 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1585 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1586 compose->gtkaspell);
1588 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1590 quote_fmt_scan_string(tmp);
1593 buf = quote_fmt_get_buffer();
1595 alertpanel_error(_("The \"From\" field of the \"Reply\" template contains an invalid email address."));
1597 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1598 quote_fmt_reset_vartable();
1603 textview = (GTK_TEXT_VIEW(compose->text));
1604 textbuf = gtk_text_view_get_buffer(textview);
1605 compose_create_tags(textview, compose);
1607 undo_block(compose->undostruct);
1609 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1610 gtkaspell_block_check(compose->gtkaspell);
1613 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1614 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1615 /* use the reply format of folder (if enabled), or the account's one
1616 (if enabled) or fallback to the global reply format, which is always
1617 enabled (even if empty), and use the relevant quotemark */
1619 if (msginfo->folder && msginfo->folder->prefs &&
1620 msginfo->folder->prefs->reply_with_format) {
1621 qmark = msginfo->folder->prefs->reply_quotemark;
1622 body_fmt = msginfo->folder->prefs->reply_body_format;
1624 } else if (account->reply_with_format) {
1625 qmark = account->reply_quotemark;
1626 body_fmt = account->reply_body_format;
1629 qmark = prefs_common.quotemark;
1630 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1631 body_fmt = gettext(prefs_common.quotefmt);
1638 /* empty quotemark is not allowed */
1639 if (qmark == NULL || *qmark == '\0')
1641 compose_quote_fmt(compose, compose->replyinfo,
1642 body_fmt, qmark, body, FALSE, TRUE,
1643 _("The body of the \"Reply\" template has an error at line %d."));
1644 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1645 quote_fmt_reset_vartable();
1648 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1649 compose_force_encryption(compose, account, FALSE, s_system);
1652 privacy_msginfo_get_signed_state(compose->replyinfo, &s_system);
1653 if (MSG_IS_SIGNED(compose->replyinfo->flags) && account->default_sign_reply) {
1654 compose_force_signing(compose, account, s_system);
1658 SIGNAL_BLOCK(textbuf);
1660 if (account->auto_sig)
1661 compose_insert_sig(compose, FALSE);
1663 compose_wrap_all(compose);
1666 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1667 gtkaspell_highlight_all(compose->gtkaspell);
1668 gtkaspell_unblock_check(compose->gtkaspell);
1670 SIGNAL_UNBLOCK(textbuf);
1672 gtk_widget_grab_focus(compose->text);
1674 undo_unblock(compose->undostruct);
1676 if (prefs_common.auto_exteditor)
1677 compose_exec_ext_editor(compose);
1679 compose->modified = FALSE;
1680 compose_set_title(compose);
1682 compose->updating = FALSE;
1683 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1684 SCROLL_TO_CURSOR(compose);
1686 if (compose->deferred_destroy) {
1687 compose_destroy(compose);
1695 #define INSERT_FW_HEADER(var, hdr) \
1696 if (msginfo->var && *msginfo->var) { \
1697 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1698 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1699 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1702 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1703 gboolean as_attach, const gchar *body,
1704 gboolean no_extedit,
1708 GtkTextView *textview;
1709 GtkTextBuffer *textbuf;
1710 gint cursor_pos = -1;
1713 cm_return_val_if_fail(msginfo != NULL, NULL);
1714 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
1717 !(account = compose_guess_forward_account_from_msginfo
1719 account = cur_account;
1721 if (!prefs_common.forward_as_attachment)
1722 mode = COMPOSE_FORWARD_INLINE;
1724 mode = COMPOSE_FORWARD;
1725 compose = compose_create(account, msginfo->folder, mode, batch);
1727 compose->updating = TRUE;
1728 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1729 if (!compose->fwdinfo)
1730 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1732 compose_extract_original_charset(compose);
1734 if (msginfo->subject && *msginfo->subject) {
1735 gchar *buf, *buf2, *p;
1737 buf = p = g_strdup(msginfo->subject);
1738 p += subject_get_prefix_length(p);
1739 memmove(buf, p, strlen(p) + 1);
1741 buf2 = g_strdup_printf("Fw: %s", buf);
1742 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1748 /* override from name according to folder properties */
1749 if (msginfo->folder && msginfo->folder->prefs &&
1750 msginfo->folder->prefs->forward_with_format &&
1751 msginfo->folder->prefs->forward_override_from_format &&
1752 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1756 MsgInfo *full_msginfo = NULL;
1759 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1761 full_msginfo = procmsg_msginfo_copy(msginfo);
1763 /* decode \-escape sequences in the internal representation of the quote format */
1764 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1765 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1768 gtkaspell_block_check(compose->gtkaspell);
1769 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1770 compose->gtkaspell);
1772 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1774 quote_fmt_scan_string(tmp);
1777 buf = quote_fmt_get_buffer();
1779 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1781 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1782 quote_fmt_reset_vartable();
1785 procmsg_msginfo_free(full_msginfo);
1788 textview = GTK_TEXT_VIEW(compose->text);
1789 textbuf = gtk_text_view_get_buffer(textview);
1790 compose_create_tags(textview, compose);
1792 undo_block(compose->undostruct);
1796 msgfile = procmsg_get_message_file(msginfo);
1797 if (!is_file_exist(msgfile))
1798 g_warning("%s: file not exist\n", msgfile);
1800 compose_attach_append(compose, msgfile, msgfile,
1801 "message/rfc822", NULL);
1805 const gchar *qmark = NULL;
1806 const gchar *body_fmt = NULL;
1807 MsgInfo *full_msginfo;
1809 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1811 full_msginfo = procmsg_msginfo_copy(msginfo);
1813 /* use the forward format of folder (if enabled), or the account's one
1814 (if enabled) or fallback to the global forward format, which is always
1815 enabled (even if empty), and use the relevant quotemark */
1816 if (msginfo->folder && msginfo->folder->prefs &&
1817 msginfo->folder->prefs->forward_with_format) {
1818 qmark = msginfo->folder->prefs->forward_quotemark;
1819 body_fmt = msginfo->folder->prefs->forward_body_format;
1821 } else if (account->forward_with_format) {
1822 qmark = account->forward_quotemark;
1823 body_fmt = account->forward_body_format;
1826 qmark = prefs_common.fw_quotemark;
1827 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1828 body_fmt = gettext(prefs_common.fw_quotefmt);
1833 /* empty quotemark is not allowed */
1834 if (qmark == NULL || *qmark == '\0')
1837 compose_quote_fmt(compose, full_msginfo,
1838 body_fmt, qmark, body, FALSE, TRUE,
1839 _("The body of the \"Forward\" template has an error at line %d."));
1840 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1841 quote_fmt_reset_vartable();
1842 compose_attach_parts(compose, msginfo);
1844 procmsg_msginfo_free(full_msginfo);
1847 SIGNAL_BLOCK(textbuf);
1849 if (account->auto_sig)
1850 compose_insert_sig(compose, FALSE);
1852 compose_wrap_all(compose);
1855 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
1856 gtkaspell_highlight_all(compose->gtkaspell);
1857 gtkaspell_unblock_check(compose->gtkaspell);
1859 SIGNAL_UNBLOCK(textbuf);
1861 cursor_pos = quote_fmt_get_cursor_pos();
1862 if (cursor_pos == -1)
1863 gtk_widget_grab_focus(compose->header_last->entry);
1865 gtk_widget_grab_focus(compose->text);
1867 if (!no_extedit && prefs_common.auto_exteditor)
1868 compose_exec_ext_editor(compose);
1871 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1872 gchar *folderidentifier;
1874 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1875 folderidentifier = folder_item_get_identifier(msginfo->folder);
1876 compose_set_save_to(compose, folderidentifier);
1877 g_free(folderidentifier);
1880 undo_unblock(compose->undostruct);
1882 compose->modified = FALSE;
1883 compose_set_title(compose);
1885 compose->updating = FALSE;
1886 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1887 SCROLL_TO_CURSOR(compose);
1889 if (compose->deferred_destroy) {
1890 compose_destroy(compose);
1894 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
1899 #undef INSERT_FW_HEADER
1901 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1904 GtkTextView *textview;
1905 GtkTextBuffer *textbuf;
1909 gboolean single_mail = TRUE;
1911 cm_return_val_if_fail(msginfo_list != NULL, NULL);
1913 if (g_slist_length(msginfo_list) > 1)
1914 single_mail = FALSE;
1916 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1917 if (((MsgInfo *)msginfo->data)->folder == NULL)
1920 /* guess account from first selected message */
1922 !(account = compose_guess_forward_account_from_msginfo
1923 (msginfo_list->data)))
1924 account = cur_account;
1926 cm_return_val_if_fail(account != NULL, NULL);
1928 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1929 if (msginfo->data) {
1930 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1931 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1935 if (msginfo_list == NULL || msginfo_list->data == NULL) {
1936 g_warning("no msginfo_list");
1940 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1942 compose->updating = TRUE;
1944 /* override from name according to folder properties */
1945 if (msginfo_list->data) {
1946 MsgInfo *msginfo = msginfo_list->data;
1948 if (msginfo->folder && msginfo->folder->prefs &&
1949 msginfo->folder->prefs->forward_with_format &&
1950 msginfo->folder->prefs->forward_override_from_format &&
1951 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1956 /* decode \-escape sequences in the internal representation of the quote format */
1957 tmp = g_malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1958 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1961 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1962 compose->gtkaspell);
1964 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1966 quote_fmt_scan_string(tmp);
1969 buf = quote_fmt_get_buffer();
1971 alertpanel_error(_("The \"From\" field of the \"Forward\" template contains an invalid email address."));
1973 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1974 quote_fmt_reset_vartable();
1980 textview = GTK_TEXT_VIEW(compose->text);
1981 textbuf = gtk_text_view_get_buffer(textview);
1982 compose_create_tags(textview, compose);
1984 undo_block(compose->undostruct);
1985 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1986 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1988 if (!is_file_exist(msgfile))
1989 g_warning("%s: file not exist\n", msgfile);
1991 compose_attach_append(compose, msgfile, msgfile,
1992 "message/rfc822", NULL);
1997 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1998 if (info->subject && *info->subject) {
1999 gchar *buf, *buf2, *p;
2001 buf = p = g_strdup(info->subject);
2002 p += subject_get_prefix_length(p);
2003 memmove(buf, p, strlen(p) + 1);
2005 buf2 = g_strdup_printf("Fw: %s", buf);
2006 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
2012 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2013 _("Fw: multiple emails"));
2016 SIGNAL_BLOCK(textbuf);
2018 if (account->auto_sig)
2019 compose_insert_sig(compose, FALSE);
2021 compose_wrap_all(compose);
2023 SIGNAL_UNBLOCK(textbuf);
2025 gtk_text_buffer_get_start_iter(textbuf, &iter);
2026 gtk_text_buffer_place_cursor(textbuf, &iter);
2028 gtk_widget_grab_focus(compose->header_last->entry);
2029 undo_unblock(compose->undostruct);
2030 compose->modified = FALSE;
2031 compose_set_title(compose);
2033 compose->updating = FALSE;
2034 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2035 SCROLL_TO_CURSOR(compose);
2037 if (compose->deferred_destroy) {
2038 compose_destroy(compose);
2042 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2047 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
2049 GtkTextIter start = *iter;
2050 GtkTextIter end_iter;
2051 int start_pos = gtk_text_iter_get_offset(&start);
2053 if (!compose->account->sig_sep)
2056 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2057 start_pos+strlen(compose->account->sig_sep));
2059 /* check sig separator */
2060 str = gtk_text_iter_get_text(&start, &end_iter);
2061 if (!strcmp(str, compose->account->sig_sep)) {
2063 /* check end of line (\n) */
2064 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2065 start_pos+strlen(compose->account->sig_sep));
2066 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2067 start_pos+strlen(compose->account->sig_sep)+1);
2068 tmp = gtk_text_iter_get_text(&start, &end_iter);
2069 if (!strcmp(tmp,"\n")) {
2081 static void compose_colorize_signature(Compose *compose)
2083 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2085 GtkTextIter end_iter;
2086 gtk_text_buffer_get_start_iter(buffer, &iter);
2087 while (gtk_text_iter_forward_line(&iter))
2088 if (compose_is_sig_separator(compose, buffer, &iter)) {
2089 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2090 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2094 #define BLOCK_WRAP() { \
2095 prev_autowrap = compose->autowrap; \
2096 buffer = gtk_text_view_get_buffer( \
2097 GTK_TEXT_VIEW(compose->text)); \
2098 compose->autowrap = FALSE; \
2100 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2101 G_CALLBACK(compose_changed_cb), \
2103 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2104 G_CALLBACK(text_inserted), \
2107 #define UNBLOCK_WRAP() { \
2108 compose->autowrap = prev_autowrap; \
2109 if (compose->autowrap) { \
2110 gint old = compose->draft_timeout_tag; \
2111 compose->draft_timeout_tag = -2; \
2112 compose_wrap_all(compose); \
2113 compose->draft_timeout_tag = old; \
2116 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2117 G_CALLBACK(compose_changed_cb), \
2119 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2120 G_CALLBACK(text_inserted), \
2124 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2126 Compose *compose = NULL;
2127 PrefsAccount *account = NULL;
2128 GtkTextView *textview;
2129 GtkTextBuffer *textbuf;
2133 gchar buf[BUFFSIZE];
2134 gboolean use_signing = FALSE;
2135 gboolean use_encryption = FALSE;
2136 gchar *privacy_system = NULL;
2137 int priority = PRIORITY_NORMAL;
2138 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2139 gboolean autowrap = prefs_common.autowrap;
2140 gboolean autoindent = prefs_common.auto_indent;
2141 HeaderEntry *manual_headers = NULL;
2143 cm_return_val_if_fail(msginfo != NULL, NULL);
2144 cm_return_val_if_fail(msginfo->folder != NULL, NULL);
2146 if (compose_put_existing_to_front(msginfo)) {
2150 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2151 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2152 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2153 gchar queueheader_buf[BUFFSIZE];
2156 /* Select Account from queue headers */
2157 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2158 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2159 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2160 account = account_find_from_id(id);
2162 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2163 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2164 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2165 account = account_find_from_id(id);
2167 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2168 sizeof(queueheader_buf), "NAID:")) {
2169 id = atoi(&queueheader_buf[strlen("NAID:")]);
2170 account = account_find_from_id(id);
2172 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2173 sizeof(queueheader_buf), "MAID:")) {
2174 id = atoi(&queueheader_buf[strlen("MAID:")]);
2175 account = account_find_from_id(id);
2177 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2178 sizeof(queueheader_buf), "S:")) {
2179 account = account_find_from_address(queueheader_buf, FALSE);
2181 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2182 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2183 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2184 use_signing = param;
2187 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2188 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2189 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2190 use_signing = param;
2193 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2194 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2195 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2196 use_encryption = param;
2198 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2199 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2200 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2201 use_encryption = param;
2203 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2204 sizeof(queueheader_buf), "X-Claws-Auto-Wrapping:")) {
2205 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Wrapping:")]);
2208 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2209 sizeof(queueheader_buf), "X-Claws-Auto-Indent:")) {
2210 param = atoi(&queueheader_buf[strlen("X-Claws-Auto-Indent:")]);
2213 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2214 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2215 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2217 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2218 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2219 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2221 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2222 sizeof(queueheader_buf), "X-Priority: ")) {
2223 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2226 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2227 sizeof(queueheader_buf), "RMID:")) {
2228 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2229 if (tokens[0] && tokens[1] && tokens[2]) {
2230 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2231 if (orig_item != NULL) {
2232 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2237 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2238 sizeof(queueheader_buf), "FMID:")) {
2239 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2240 if (tokens[0] && tokens[1] && tokens[2]) {
2241 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2242 if (orig_item != NULL) {
2243 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2248 /* Get manual headers */
2249 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "X-Claws-Manual-Headers:")) {
2250 gchar *listmh = g_strdup(&queueheader_buf[strlen("X-Claws-Manual-Headers:")]);
2251 if (*listmh != '\0') {
2252 debug_print("Got manual headers: %s\n", listmh);
2253 manual_headers = procheader_entries_from_str(listmh);
2258 account = msginfo->folder->folder->account;
2261 if (!account && prefs_common.reedit_account_autosel) {
2262 gchar from[BUFFSIZE];
2263 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2264 extract_address(from);
2265 account = account_find_from_address(from, FALSE);
2269 account = cur_account;
2271 cm_return_val_if_fail(account != NULL, NULL);
2273 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2275 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", autowrap);
2276 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", autoindent);
2277 compose->autowrap = autowrap;
2278 compose->replyinfo = replyinfo;
2279 compose->fwdinfo = fwdinfo;
2281 compose->updating = TRUE;
2282 compose->priority = priority;
2284 if (privacy_system != NULL) {
2285 compose->privacy_system = privacy_system;
2286 compose_use_signing(compose, use_signing);
2287 compose_use_encryption(compose, use_encryption);
2288 compose_update_privacy_system_menu_item(compose, FALSE);
2290 activate_privacy_system(compose, account, FALSE);
2293 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2295 compose_extract_original_charset(compose);
2297 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2298 folder_has_parent_of_type(msginfo->folder, F_DRAFT) ||
2299 folder_has_parent_of_type(msginfo->folder, F_OUTBOX)) {
2300 gchar queueheader_buf[BUFFSIZE];
2302 /* Set message save folder */
2303 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2304 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2305 compose_set_save_to(compose, &queueheader_buf[4]);
2307 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2308 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2310 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/RequestRetRcpt", TRUE);
2315 if (compose_parse_header(compose, msginfo) < 0) {
2316 compose->updating = FALSE;
2317 compose_destroy(compose);
2320 compose_reedit_set_entry(compose, msginfo);
2322 textview = GTK_TEXT_VIEW(compose->text);
2323 textbuf = gtk_text_view_get_buffer(textview);
2324 compose_create_tags(textview, compose);
2326 mark = gtk_text_buffer_get_insert(textbuf);
2327 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2329 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2330 G_CALLBACK(compose_changed_cb),
2333 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2334 fp = procmime_get_first_encrypted_text_content(msginfo);
2336 compose_force_encryption(compose, account, TRUE, NULL);
2339 fp = procmime_get_first_text_content(msginfo);
2342 g_warning("Can't get text part\n");
2346 gboolean prev_autowrap;
2347 GtkTextBuffer *buffer;
2349 while (fgets(buf, sizeof(buf), fp) != NULL) {
2351 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2357 compose_attach_parts(compose, msginfo);
2359 compose_colorize_signature(compose);
2361 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2362 G_CALLBACK(compose_changed_cb),
2365 if (manual_headers != NULL) {
2366 if (compose_parse_manual_headers(compose, msginfo, manual_headers) < 0) {
2367 procheader_entries_free(manual_headers);
2368 compose->updating = FALSE;
2369 compose_destroy(compose);
2372 procheader_entries_free(manual_headers);
2375 gtk_widget_grab_focus(compose->text);
2377 if (prefs_common.auto_exteditor) {
2378 compose_exec_ext_editor(compose);
2380 compose->modified = FALSE;
2381 compose_set_title(compose);
2383 compose->updating = FALSE;
2384 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2385 SCROLL_TO_CURSOR(compose);
2387 if (compose->deferred_destroy) {
2388 compose_destroy(compose);
2392 compose->sig_str = account_get_signature_str(compose->account);
2394 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2399 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2406 cm_return_val_if_fail(msginfo != NULL, NULL);
2409 account = account_get_reply_account(msginfo,
2410 prefs_common.reply_account_autosel);
2411 cm_return_val_if_fail(account != NULL, NULL);
2413 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2415 compose->updating = TRUE;
2417 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2418 compose->replyinfo = NULL;
2419 compose->fwdinfo = NULL;
2421 compose_show_first_last_header(compose, TRUE);
2423 gtk_widget_grab_focus(compose->header_last->entry);
2425 filename = procmsg_get_message_file(msginfo);
2427 if (filename == NULL) {
2428 compose->updating = FALSE;
2429 compose_destroy(compose);
2434 compose->redirect_filename = filename;
2436 /* Set save folder */
2437 item = msginfo->folder;
2438 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2439 gchar *folderidentifier;
2441 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2442 folderidentifier = folder_item_get_identifier(item);
2443 compose_set_save_to(compose, folderidentifier);
2444 g_free(folderidentifier);
2447 compose_attach_parts(compose, msginfo);
2449 if (msginfo->subject)
2450 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2452 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2454 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2455 _("The body of the \"Redirect\" template has an error at line %d."));
2456 quote_fmt_reset_vartable();
2457 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2459 compose_colorize_signature(compose);
2462 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Add", FALSE);
2463 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", FALSE);
2464 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", FALSE);
2466 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Save", FALSE);
2467 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", FALSE);
2468 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/AttachFile", FALSE);
2469 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", FALSE);
2470 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", FALSE);
2471 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", FALSE);
2472 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/ShowRuler", FALSE);
2473 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Actions", FALSE);
2475 if (compose->toolbar->draft_btn)
2476 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2477 if (compose->toolbar->insert_btn)
2478 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2479 if (compose->toolbar->attach_btn)
2480 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2481 if (compose->toolbar->sig_btn)
2482 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2483 if (compose->toolbar->exteditor_btn)
2484 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2485 if (compose->toolbar->linewrap_current_btn)
2486 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2487 if (compose->toolbar->linewrap_all_btn)
2488 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2490 compose->modified = FALSE;
2491 compose_set_title(compose);
2492 compose->updating = FALSE;
2493 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2494 SCROLL_TO_CURSOR(compose);
2496 if (compose->deferred_destroy) {
2497 compose_destroy(compose);
2501 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
2506 GList *compose_get_compose_list(void)
2508 return compose_list;
2511 void compose_entry_append(Compose *compose, const gchar *address,
2512 ComposeEntryType type, ComposePrefType pref_type)
2514 const gchar *header;
2516 gboolean in_quote = FALSE;
2517 if (!address || *address == '\0') return;
2524 header = N_("Bcc:");
2526 case COMPOSE_REPLYTO:
2527 header = N_("Reply-To:");
2529 case COMPOSE_NEWSGROUPS:
2530 header = N_("Newsgroups:");
2532 case COMPOSE_FOLLOWUPTO:
2533 header = N_( "Followup-To:");
2535 case COMPOSE_INREPLYTO:
2536 header = N_( "In-Reply-To:");
2543 header = prefs_common_translated_header_name(header);
2545 cur = begin = (gchar *)address;
2547 /* we separate the line by commas, but not if we're inside a quoted
2549 while (*cur != '\0') {
2551 in_quote = !in_quote;
2552 if (*cur == ',' && !in_quote) {
2553 gchar *tmp = g_strdup(begin);
2555 tmp[cur-begin]='\0';
2558 while (*tmp == ' ' || *tmp == '\t')
2560 compose_add_header_entry(compose, header, tmp, pref_type);
2567 gchar *tmp = g_strdup(begin);
2569 tmp[cur-begin]='\0';
2570 while (*tmp == ' ' || *tmp == '\t')
2572 compose_add_header_entry(compose, header, tmp, pref_type);
2577 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2579 #if !GTK_CHECK_VERSION(3, 0, 0)
2580 static GdkColor yellow;
2581 static GdkColor black;
2582 static gboolean yellow_initialised = FALSE;
2584 static GdkColor yellow = { (guint32)0, (guint16)0xf5, (guint16)0xf6, (guint16)0xbe };
2585 static GdkColor black = { (guint32)0, (guint16)0x0, (guint16)0x0, (guint16)0x0 };
2590 #if !GTK_CHECK_VERSION(3, 0, 0)
2591 if (!yellow_initialised) {
2592 gdk_color_parse("#f5f6be", &yellow);
2593 gdk_color_parse("#000000", &black);
2594 yellow_initialised = gdk_colormap_alloc_color(
2595 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2596 yellow_initialised &= gdk_colormap_alloc_color(
2597 gdk_colormap_get_system(), &black, FALSE, TRUE);
2601 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2602 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2603 if (gtk_entry_get_text(entry) &&
2604 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2605 #if !GTK_CHECK_VERSION(3, 0, 0)
2606 if (yellow_initialised) {
2608 gtk_widget_modify_base(
2609 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2610 GTK_STATE_NORMAL, &yellow);
2611 gtk_widget_modify_text(
2612 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2613 GTK_STATE_NORMAL, &black);
2614 #if !GTK_CHECK_VERSION(3, 0, 0)
2621 void compose_toolbar_cb(gint action, gpointer data)
2623 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2624 Compose *compose = (Compose*)toolbar_item->parent;
2626 cm_return_if_fail(compose != NULL);
2630 compose_send_cb(NULL, compose);
2633 compose_send_later_cb(NULL, compose);
2636 compose_draft(compose, COMPOSE_QUIT_EDITING);
2639 compose_insert_file_cb(NULL, compose);
2642 compose_attach_cb(NULL, compose);
2645 compose_insert_sig(compose, FALSE);
2648 compose_ext_editor_cb(NULL, compose);
2650 case A_LINEWRAP_CURRENT:
2651 compose_beautify_paragraph(compose, NULL, TRUE);
2653 case A_LINEWRAP_ALL:
2654 compose_wrap_all_full(compose, TRUE);
2657 compose_address_cb(NULL, compose);
2660 case A_CHECK_SPELLING:
2661 compose_check_all(NULL, compose);
2669 static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2674 gchar *subject = NULL;
2678 gchar **attach = NULL;
2679 gchar *inreplyto = NULL;
2680 MailField mfield = NO_FIELD_PRESENT;
2682 /* get mailto parts but skip from */
2683 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach, &inreplyto);
2686 compose_entry_append(compose, to, to_type, PREF_MAILTO);
2687 mfield = TO_FIELD_PRESENT;
2690 compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
2692 compose_entry_append(compose, bcc, COMPOSE_BCC, PREF_MAILTO);
2694 if (!g_utf8_validate (subject, -1, NULL)) {
2695 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2696 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2699 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2701 mfield = SUBJECT_FIELD_PRESENT;
2704 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2705 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2708 gboolean prev_autowrap = compose->autowrap;
2710 compose->autowrap = FALSE;
2712 mark = gtk_text_buffer_get_insert(buffer);
2713 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2715 if (!g_utf8_validate (body, -1, NULL)) {
2716 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2717 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2720 gtk_text_buffer_insert(buffer, &iter, body, -1);
2722 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2724 compose->autowrap = prev_autowrap;
2725 if (compose->autowrap)
2726 compose_wrap_all(compose);
2727 mfield = BODY_FIELD_PRESENT;
2731 gint i = 0, att = 0;
2732 gchar *warn_files = NULL;
2733 while (attach[i] != NULL) {
2734 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2735 if (utf8_filename) {
2736 if (compose_attach_append(compose, attach[i], utf8_filename, NULL, NULL)) {
2737 gchar *tmp = g_strdup_printf("%s%s\n",
2738 warn_files?warn_files:"",
2744 g_free(utf8_filename);
2746 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2751 alertpanel_notice(ngettext(
2752 "The following file has been attached: \n%s",
2753 "The following files have been attached: \n%s", att), warn_files);
2758 compose_entry_append(compose, inreplyto, COMPOSE_INREPLYTO, PREF_MAILTO);
2771 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2773 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2774 {"Cc:", NULL, TRUE},
2775 {"References:", NULL, FALSE},
2776 {"Bcc:", NULL, TRUE},
2777 {"Newsgroups:", NULL, TRUE},
2778 {"Followup-To:", NULL, TRUE},
2779 {"List-Post:", NULL, FALSE},
2780 {"X-Priority:", NULL, FALSE},
2781 {NULL, NULL, FALSE}};
2797 cm_return_val_if_fail(msginfo != NULL, -1);
2799 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2800 procheader_get_header_fields(fp, hentry);
2803 if (hentry[H_REPLY_TO].body != NULL) {
2804 if (hentry[H_REPLY_TO].body[0] != '\0') {
2806 conv_unmime_header(hentry[H_REPLY_TO].body,
2809 g_free(hentry[H_REPLY_TO].body);
2810 hentry[H_REPLY_TO].body = NULL;
2812 if (hentry[H_CC].body != NULL) {
2813 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL, TRUE);
2814 g_free(hentry[H_CC].body);
2815 hentry[H_CC].body = NULL;
2817 if (hentry[H_REFERENCES].body != NULL) {
2818 if (compose->mode == COMPOSE_REEDIT)
2819 compose->references = hentry[H_REFERENCES].body;
2821 compose->references = compose_parse_references
2822 (hentry[H_REFERENCES].body, msginfo->msgid);
2823 g_free(hentry[H_REFERENCES].body);
2825 hentry[H_REFERENCES].body = NULL;
2827 if (hentry[H_BCC].body != NULL) {
2828 if (compose->mode == COMPOSE_REEDIT)
2830 conv_unmime_header(hentry[H_BCC].body, NULL, TRUE);
2831 g_free(hentry[H_BCC].body);
2832 hentry[H_BCC].body = NULL;
2834 if (hentry[H_NEWSGROUPS].body != NULL) {
2835 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2836 hentry[H_NEWSGROUPS].body = NULL;
2838 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2839 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2840 compose->followup_to =
2841 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2844 g_free(hentry[H_FOLLOWUP_TO].body);
2845 hentry[H_FOLLOWUP_TO].body = NULL;
2847 if (hentry[H_LIST_POST].body != NULL) {
2848 gchar *to = NULL, *start = NULL;
2850 extract_address(hentry[H_LIST_POST].body);
2851 if (hentry[H_LIST_POST].body[0] != '\0') {
2852 start = strstr(hentry[H_LIST_POST].body, "mailto:");
2854 scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
2855 NULL, &to, NULL, NULL, NULL, NULL, NULL, NULL);
2858 g_free(compose->ml_post);
2859 compose->ml_post = to;
2862 g_free(hentry[H_LIST_POST].body);
2863 hentry[H_LIST_POST].body = NULL;
2866 /* CLAWS - X-Priority */
2867 if (compose->mode == COMPOSE_REEDIT)
2868 if (hentry[H_X_PRIORITY].body != NULL) {
2871 priority = atoi(hentry[H_X_PRIORITY].body);
2872 g_free(hentry[H_X_PRIORITY].body);
2874 hentry[H_X_PRIORITY].body = NULL;
2876 if (priority < PRIORITY_HIGHEST ||
2877 priority > PRIORITY_LOWEST)
2878 priority = PRIORITY_NORMAL;
2880 compose->priority = priority;
2883 if (compose->mode == COMPOSE_REEDIT) {
2884 if (msginfo->inreplyto && *msginfo->inreplyto)
2885 compose->inreplyto = g_strdup(msginfo->inreplyto);
2889 if (msginfo->msgid && *msginfo->msgid)
2890 compose->inreplyto = g_strdup(msginfo->msgid);
2892 if (!compose->references) {
2893 if (msginfo->msgid && *msginfo->msgid) {
2894 if (msginfo->inreplyto && *msginfo->inreplyto)
2895 compose->references =
2896 g_strdup_printf("<%s>\n\t<%s>",
2900 compose->references =
2901 g_strconcat("<", msginfo->msgid, ">",
2903 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2904 compose->references =
2905 g_strconcat("<", msginfo->inreplyto, ">",
2913 static gint compose_parse_manual_headers(Compose *compose, MsgInfo *msginfo, HeaderEntry *entries)
2918 cm_return_val_if_fail(msginfo != NULL, -1);
2920 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2921 procheader_get_header_fields(fp, entries);
2925 while (he != NULL && he->name != NULL) {
2927 GtkListStore *model = NULL;
2929 debug_print("Adding manual header: %s with value %s\n", he->name, he->body);
2930 model = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(compose->header_last->combo)));
2931 COMBOBOX_ADD(model, he->name, COMPOSE_TO);
2932 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(compose->header_last->combo), &iter);
2933 gtk_entry_set_text(GTK_ENTRY(compose->header_last->entry), he->body);
2940 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2942 GSList *ref_id_list, *cur;
2946 ref_id_list = references_list_append(NULL, ref);
2947 if (!ref_id_list) return NULL;
2948 if (msgid && *msgid)
2949 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2954 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2955 /* "<" + Message-ID + ">" + CR+LF+TAB */
2956 len += strlen((gchar *)cur->data) + 5;
2958 if (len > MAX_REFERENCES_LEN) {
2959 /* remove second message-ID */
2960 if (ref_id_list && ref_id_list->next &&
2961 ref_id_list->next->next) {
2962 g_free(ref_id_list->next->data);
2963 ref_id_list = g_slist_remove
2964 (ref_id_list, ref_id_list->next->data);
2966 slist_free_strings_full(ref_id_list);
2973 new_ref = g_string_new("");
2974 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2975 if (new_ref->len > 0)
2976 g_string_append(new_ref, "\n\t");
2977 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2980 slist_free_strings_full(ref_id_list);
2982 new_ref_str = new_ref->str;
2983 g_string_free(new_ref, FALSE);
2988 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2989 const gchar *fmt, const gchar *qmark,
2990 const gchar *body, gboolean rewrap,
2991 gboolean need_unescape,
2992 const gchar *err_msg)
2994 MsgInfo* dummyinfo = NULL;
2995 gchar *quote_str = NULL;
2997 gboolean prev_autowrap;
2998 const gchar *trimmed_body = body;
2999 gint cursor_pos = -1;
3000 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3001 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3006 SIGNAL_BLOCK(buffer);
3009 dummyinfo = compose_msginfo_new_from_compose(compose);
3010 msginfo = dummyinfo;
3013 if (qmark != NULL) {
3015 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
3016 compose->gtkaspell);
3018 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
3020 quote_fmt_scan_string(qmark);
3023 buf = quote_fmt_get_buffer();
3025 alertpanel_error(_("The \"Quotation mark\" of the template is invalid."));
3027 Xstrdup_a(quote_str, buf, goto error)
3030 if (fmt && *fmt != '\0') {
3033 while (*trimmed_body == '\n')
3037 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
3038 compose->gtkaspell);
3040 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
3042 if (need_unescape) {
3045 /* decode \-escape sequences in the internal representation of the quote format */
3046 tmp = g_malloc(strlen(fmt)+1);
3047 pref_get_unescaped_pref(tmp, fmt);
3048 quote_fmt_scan_string(tmp);
3052 quote_fmt_scan_string(fmt);
3056 buf = quote_fmt_get_buffer();
3058 gint line = quote_fmt_get_line();
3059 alertpanel_error(err_msg, line);
3065 prev_autowrap = compose->autowrap;
3066 compose->autowrap = FALSE;
3068 mark = gtk_text_buffer_get_insert(buffer);
3069 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3070 if (g_utf8_validate(buf, -1, NULL)) {
3071 gtk_text_buffer_insert(buffer, &iter, buf, -1);
3073 gchar *tmpout = NULL;
3074 tmpout = conv_codeset_strdup
3075 (buf, conv_get_locale_charset_str_no_utf8(),
3077 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
3079 tmpout = g_malloc(strlen(buf)*2+1);
3080 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
3082 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
3086 cursor_pos = quote_fmt_get_cursor_pos();
3087 if (cursor_pos == -1)
3088 cursor_pos = gtk_text_iter_get_offset(&iter);
3089 compose->set_cursor_pos = cursor_pos;
3091 gtk_text_buffer_get_start_iter(buffer, &iter);
3092 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
3093 gtk_text_buffer_place_cursor(buffer, &iter);
3095 compose->autowrap = prev_autowrap;
3096 if (compose->autowrap && rewrap)
3097 compose_wrap_all(compose);
3104 SIGNAL_UNBLOCK(buffer);
3106 procmsg_msginfo_free( dummyinfo );
3111 /* if ml_post is of type addr@host and from is of type
3112 * addr-anything@host, return TRUE
3114 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
3116 gchar *left_ml = NULL;
3117 gchar *right_ml = NULL;
3118 gchar *left_from = NULL;
3119 gchar *right_from = NULL;
3120 gboolean result = FALSE;
3122 if (!ml_post || !from)
3125 left_ml = g_strdup(ml_post);
3126 if (strstr(left_ml, "@")) {
3127 right_ml = strstr(left_ml, "@")+1;
3128 *(strstr(left_ml, "@")) = '\0';
3131 left_from = g_strdup(from);
3132 if (strstr(left_from, "@")) {
3133 right_from = strstr(left_from, "@")+1;
3134 *(strstr(left_from, "@")) = '\0';
3137 if (left_ml && left_from && right_ml && right_from
3138 && !strncmp(left_from, left_ml, strlen(left_ml))
3139 && !strcmp(right_from, right_ml)) {
3148 static void compose_set_folder_prefs(Compose *compose, FolderItem *folder,
3149 gboolean respect_default_to)
3153 if (!folder || !folder->prefs)
3156 if (respect_default_to && folder->prefs->enable_default_to) {
3157 compose_entry_append(compose, folder->prefs->default_to,
3158 COMPOSE_TO, PREF_FOLDER);
3159 compose_entry_mark_default_to(compose, folder->prefs->default_to);
3161 if (folder->prefs->enable_default_cc)
3162 compose_entry_append(compose, folder->prefs->default_cc,
3163 COMPOSE_CC, PREF_FOLDER);
3164 if (folder->prefs->enable_default_bcc)
3165 compose_entry_append(compose, folder->prefs->default_bcc,
3166 COMPOSE_BCC, PREF_FOLDER);
3167 if (folder->prefs->enable_default_replyto)
3168 compose_entry_append(compose, folder->prefs->default_replyto,
3169 COMPOSE_REPLYTO, PREF_FOLDER);
3172 static void compose_reply_set_subject(Compose *compose, MsgInfo *msginfo)
3177 if (!compose || !msginfo)
3180 if (msginfo->subject && *msginfo->subject) {
3181 buf = p = g_strdup(msginfo->subject);
3182 p += subject_get_prefix_length(p);
3183 memmove(buf, p, strlen(p) + 1);
3185 buf2 = g_strdup_printf("Re: %s", buf);
3186 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3191 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3194 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3195 gboolean to_all, gboolean to_ml,
3197 gboolean followup_and_reply_to)
3199 GSList *cc_list = NULL;
3202 gchar *replyto = NULL;
3203 gchar *ac_email = NULL;
3205 gboolean reply_to_ml = FALSE;
3206 gboolean default_reply_to = FALSE;
3208 cm_return_if_fail(compose->account != NULL);
3209 cm_return_if_fail(msginfo != NULL);
3211 reply_to_ml = to_ml && compose->ml_post;
3213 default_reply_to = msginfo->folder &&
3214 msginfo->folder->prefs->enable_default_reply_to;
3216 if (compose->account->protocol != A_NNTP) {
3217 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
3219 if (reply_to_ml && !default_reply_to) {
3221 gboolean is_subscr = is_subscription(compose->ml_post,
3224 /* normal answer to ml post with a reply-to */
3225 compose_entry_append(compose,
3227 COMPOSE_TO, PREF_ML);
3228 if (compose->replyto)
3229 compose_entry_append(compose,
3231 COMPOSE_CC, PREF_ML);
3233 /* answer to subscription confirmation */
3234 if (compose->replyto)
3235 compose_entry_append(compose,
3237 COMPOSE_TO, PREF_ML);
3238 else if (msginfo->from)
3239 compose_entry_append(compose,
3241 COMPOSE_TO, PREF_ML);
3244 else if (!(to_all || to_sender) && default_reply_to) {
3245 compose_entry_append(compose,
3246 msginfo->folder->prefs->default_reply_to,
3247 COMPOSE_TO, PREF_FOLDER);
3248 compose_entry_mark_default_to(compose,
3249 msginfo->folder->prefs->default_reply_to);
3254 Xstrdup_a(tmp1, msginfo->from, return);
3255 extract_address(tmp1);
3256 if (to_all || to_sender ||
3257 !account_find_from_address(tmp1, FALSE))
3258 compose_entry_append(compose,
3259 (compose->replyto && !to_sender)
3260 ? compose->replyto :
3261 msginfo->from ? msginfo->from : "",
3262 COMPOSE_TO, PREF_NONE);
3263 else if (!to_all && !to_sender) {
3264 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3265 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3266 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3267 if (compose->replyto) {
3268 compose_entry_append(compose,
3270 COMPOSE_TO, PREF_NONE);
3272 compose_entry_append(compose,
3273 msginfo->from ? msginfo->from : "",
3274 COMPOSE_TO, PREF_NONE);
3277 /* replying to own mail, use original recp */
3278 compose_entry_append(compose,
3279 msginfo->to ? msginfo->to : "",
3280 COMPOSE_TO, PREF_NONE);
3281 compose_entry_append(compose,
3282 msginfo->cc ? msginfo->cc : "",
3283 COMPOSE_CC, PREF_NONE);
3288 if (to_sender || (compose->followup_to &&
3289 !strncmp(compose->followup_to, "poster", 6)))
3290 compose_entry_append
3292 (compose->replyto ? compose->replyto :
3293 msginfo->from ? msginfo->from : ""),
3294 COMPOSE_TO, PREF_NONE);
3296 else if (followup_and_reply_to || to_all) {
3297 compose_entry_append
3299 (compose->replyto ? compose->replyto :
3300 msginfo->from ? msginfo->from : ""),
3301 COMPOSE_TO, PREF_NONE);
3303 compose_entry_append
3305 compose->followup_to ? compose->followup_to :
3306 compose->newsgroups ? compose->newsgroups : "",
3307 COMPOSE_NEWSGROUPS, PREF_NONE);
3310 compose_entry_append
3312 compose->followup_to ? compose->followup_to :
3313 compose->newsgroups ? compose->newsgroups : "",
3314 COMPOSE_NEWSGROUPS, PREF_NONE);
3316 compose_reply_set_subject(compose, msginfo);
3318 if (to_ml && compose->ml_post) return;
3319 if (!to_all || compose->account->protocol == A_NNTP) return;
3321 if (compose->replyto) {
3322 Xstrdup_a(replyto, compose->replyto, return);
3323 extract_address(replyto);
3325 if (msginfo->from) {
3326 Xstrdup_a(from, msginfo->from, return);
3327 extract_address(from);
3330 if (replyto && from)
3331 cc_list = address_list_append_with_comments(cc_list, from);
3332 if (to_all && msginfo->folder &&
3333 msginfo->folder->prefs->enable_default_reply_to)
3334 cc_list = address_list_append_with_comments(cc_list,
3335 msginfo->folder->prefs->default_reply_to);
3336 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3337 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3339 ac_email = g_utf8_strdown(compose->account->address, -1);
3342 for (cur = cc_list; cur != NULL; cur = cur->next) {
3343 gchar *addr = g_utf8_strdown(cur->data, -1);
3344 extract_address(addr);
3346 if (strcmp(ac_email, addr))
3347 compose_entry_append(compose, (gchar *)cur->data,
3348 COMPOSE_CC, PREF_NONE);
3350 debug_print("Cc address same as compose account's, ignoring\n");
3355 slist_free_strings_full(cc_list);
3361 #define SET_ENTRY(entry, str) \
3364 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3367 #define SET_ADDRESS(type, str) \
3370 compose_entry_append(compose, str, type, PREF_NONE); \
3373 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3375 cm_return_if_fail(msginfo != NULL);
3377 SET_ENTRY(subject_entry, msginfo->subject);
3378 SET_ENTRY(from_name, msginfo->from);
3379 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3380 SET_ADDRESS(COMPOSE_CC, compose->cc);
3381 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3382 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3383 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3384 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3386 compose_update_priority_menu_item(compose);
3387 compose_update_privacy_system_menu_item(compose, FALSE);
3388 compose_show_first_last_header(compose, TRUE);
3394 static void compose_insert_sig(Compose *compose, gboolean replace)
3396 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3397 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3399 GtkTextIter iter, iter_end;
3400 gint cur_pos, ins_pos;
3401 gboolean prev_autowrap;
3402 gboolean found = FALSE;
3403 gboolean exists = FALSE;
3405 cm_return_if_fail(compose->account != NULL);
3409 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3410 G_CALLBACK(compose_changed_cb),
3413 mark = gtk_text_buffer_get_insert(buffer);
3414 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3415 cur_pos = gtk_text_iter_get_offset (&iter);
3418 gtk_text_buffer_get_end_iter(buffer, &iter);
3420 exists = (compose->sig_str != NULL);
3423 GtkTextIter first_iter, start_iter, end_iter;
3425 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3427 if (!exists || compose->sig_str[0] == '\0')
3430 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3431 compose->signature_tag);
3434 /* include previous \n\n */
3435 gtk_text_iter_backward_chars(&first_iter, 1);
3436 start_iter = first_iter;
3437 end_iter = first_iter;
3439 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3440 compose->signature_tag);
3441 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3442 compose->signature_tag);
3444 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3450 g_free(compose->sig_str);
3451 compose->sig_str = account_get_signature_str(compose->account);
3453 cur_pos = gtk_text_iter_get_offset(&iter);
3455 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3456 g_free(compose->sig_str);
3457 compose->sig_str = NULL;
3459 if (compose->sig_inserted == FALSE)
3460 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3461 compose->sig_inserted = TRUE;
3463 cur_pos = gtk_text_iter_get_offset(&iter);
3464 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3466 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3467 gtk_text_iter_forward_chars(&iter, 1);
3468 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3469 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3471 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3472 cur_pos = gtk_text_buffer_get_char_count (buffer);
3475 /* put the cursor where it should be
3476 * either where the quote_fmt says, either where it was */
3477 if (compose->set_cursor_pos < 0)
3478 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3480 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3481 compose->set_cursor_pos);
3483 compose->set_cursor_pos = -1;
3484 gtk_text_buffer_place_cursor(buffer, &iter);
3485 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3486 G_CALLBACK(compose_changed_cb),
3492 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3495 GtkTextBuffer *buffer;
3498 const gchar *cur_encoding;
3499 gchar buf[BUFFSIZE];
3502 gboolean prev_autowrap;
3503 gboolean badtxt = FALSE;
3504 struct stat file_stat;
3507 cm_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3509 /* get the size of the file we are about to insert */
3510 ret = g_stat(file, &file_stat);
3512 gchar *shortfile = g_path_get_basename(file);
3513 alertpanel_error(_("Could not get size of file '%s'."), shortfile);
3515 return COMPOSE_INSERT_NO_FILE;
3516 } else if (prefs_common.warn_large_insert == TRUE) {
3518 /* ask user for confirmation if the file is large */
3519 if (prefs_common.warn_large_insert_size < 0 ||
3520 file_stat.st_size > (prefs_common.warn_large_insert_size * 1024)) {
3524 msg = g_strdup_printf(_("You are about to insert a file of %s "
3525 "in the message body. Are you sure you want to do that?"),
3526 to_human_readable(file_stat.st_size));
3527 aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
3528 _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
3531 /* do we ask for confirmation next time? */
3532 if (aval & G_ALERTDISABLE) {
3533 /* no confirmation next time, disable feature in preferences */
3534 aval &= ~G_ALERTDISABLE;
3535 prefs_common.warn_large_insert = FALSE;
3538 /* abort file insertion if user canceled action */
3539 if (aval != G_ALERTALTERNATE) {
3540 return COMPOSE_INSERT_NO_FILE;
3546 if ((fp = g_fopen(file, "rb")) == NULL) {
3547 FILE_OP_ERROR(file, "fopen");
3548 return COMPOSE_INSERT_READ_ERROR;
3551 prev_autowrap = compose->autowrap;
3552 compose->autowrap = FALSE;
3554 text = GTK_TEXT_VIEW(compose->text);
3555 buffer = gtk_text_view_get_buffer(text);
3556 mark = gtk_text_buffer_get_insert(buffer);
3557 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3559 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3560 G_CALLBACK(text_inserted),
3563 cur_encoding = conv_get_locale_charset_str_no_utf8();
3565 while (fgets(buf, sizeof(buf), fp) != NULL) {
3568 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3569 str = g_strdup(buf);
3571 str = conv_codeset_strdup
3572 (buf, cur_encoding, CS_INTERNAL);
3575 /* strip <CR> if DOS/Windows file,
3576 replace <CR> with <LF> if Macintosh file. */
3579 if (len > 0 && str[len - 1] != '\n') {
3581 if (str[len] == '\r') str[len] = '\n';
3584 gtk_text_buffer_insert(buffer, &iter, str, -1);
3588 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3589 G_CALLBACK(text_inserted),
3591 compose->autowrap = prev_autowrap;
3592 if (compose->autowrap)
3593 compose_wrap_all(compose);
3598 return COMPOSE_INSERT_INVALID_CHARACTER;
3600 return COMPOSE_INSERT_SUCCESS;
3603 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3604 const gchar *filename,
3605 const gchar *content_type,
3606 const gchar *charset)
3614 GtkListStore *store;
3616 gboolean has_binary = FALSE;
3618 if (!is_file_exist(file)) {
3619 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3620 gboolean result = FALSE;
3621 if (file_from_uri && is_file_exist(file_from_uri)) {
3622 result = compose_attach_append(
3623 compose, file_from_uri,
3624 filename, content_type,
3627 g_free(file_from_uri);
3630 alertpanel_error("File %s doesn't exist\n", filename);
3633 if ((size = get_file_size(file)) < 0) {
3634 alertpanel_error("Can't get file size of %s\n", filename);
3638 alertpanel_error(_("File %s is empty."), filename);
3641 if ((fp = g_fopen(file, "rb")) == NULL) {
3642 alertpanel_error(_("Can't read %s."), filename);
3647 ainfo = g_new0(AttachInfo, 1);
3648 auto_ainfo = g_auto_pointer_new_with_free
3649 (ainfo, (GFreeFunc) compose_attach_info_free);
3650 ainfo->file = g_strdup(file);
3653 ainfo->content_type = g_strdup(content_type);
3654 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3656 MsgFlags flags = {0, 0};
3658 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3659 ainfo->encoding = ENC_7BIT;
3661 ainfo->encoding = ENC_8BIT;
3663 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3664 if (msginfo && msginfo->subject)
3665 name = g_strdup(msginfo->subject);
3667 name = g_path_get_basename(filename ? filename : file);
3669 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3671 procmsg_msginfo_free(msginfo);
3673 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
3674 ainfo->charset = g_strdup(charset);
3675 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3677 ainfo->encoding = ENC_BASE64;
3679 name = g_path_get_basename(filename ? filename : file);
3680 ainfo->name = g_strdup(name);
3684 ainfo->content_type = procmime_get_mime_type(file);
3685 if (!ainfo->content_type) {
3686 ainfo->content_type =
3687 g_strdup("application/octet-stream");
3688 ainfo->encoding = ENC_BASE64;
3689 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text/", 5))
3691 procmime_get_encoding_for_text_file(file, &has_binary);
3693 ainfo->encoding = ENC_BASE64;
3694 name = g_path_get_basename(filename ? filename : file);
3695 ainfo->name = g_strdup(name);
3699 if (ainfo->name != NULL
3700 && !strcmp(ainfo->name, ".")) {
3701 g_free(ainfo->name);
3705 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3706 g_free(ainfo->content_type);
3707 ainfo->content_type = g_strdup("application/octet-stream");
3708 g_free(ainfo->charset);
3709 ainfo->charset = NULL;
3712 ainfo->size = (goffset)size;
3713 size_text = to_human_readable((goffset)size);
3715 store = GTK_LIST_STORE(gtk_tree_view_get_model
3716 (GTK_TREE_VIEW(compose->attach_clist)));
3718 gtk_list_store_append(store, &iter);
3719 gtk_list_store_set(store, &iter,
3720 COL_MIMETYPE, ainfo->content_type,
3721 COL_SIZE, size_text,
3722 COL_NAME, ainfo->name,
3723 COL_CHARSET, ainfo->charset,
3725 COL_AUTODATA, auto_ainfo,
3728 g_auto_pointer_free(auto_ainfo);
3729 compose_attach_update_label(compose);
3733 static void compose_use_signing(Compose *compose, gboolean use_signing)
3735 compose->use_signing = use_signing;
3736 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", use_signing);
3739 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3741 compose->use_encryption = use_encryption;
3742 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", use_encryption);
3745 #define NEXT_PART_NOT_CHILD(info) \
3747 node = info->node; \
3748 while (node->children) \
3749 node = g_node_last_child(node); \
3750 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3753 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3757 MimeInfo *firsttext = NULL;
3758 MimeInfo *encrypted = NULL;
3761 const gchar *partname = NULL;
3763 mimeinfo = procmime_scan_message(msginfo);
3764 if (!mimeinfo) return;
3766 if (mimeinfo->node->children == NULL) {
3767 procmime_mimeinfo_free_all(mimeinfo);
3771 /* find first content part */
3772 child = (MimeInfo *) mimeinfo->node->children->data;
3773 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3774 child = (MimeInfo *)child->node->children->data;
3777 if (child->type == MIMETYPE_TEXT) {
3779 debug_print("First text part found\n");
3780 } else if (compose->mode == COMPOSE_REEDIT &&
3781 child->type == MIMETYPE_APPLICATION &&
3782 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3783 encrypted = (MimeInfo *)child->node->parent->data;
3786 child = (MimeInfo *) mimeinfo->node->children->data;
3787 while (child != NULL) {
3790 if (child == encrypted) {
3791 /* skip this part of tree */
3792 NEXT_PART_NOT_CHILD(child);
3796 if (child->type == MIMETYPE_MULTIPART) {
3797 /* get the actual content */
3798 child = procmime_mimeinfo_next(child);
3802 if (child == firsttext) {
3803 child = procmime_mimeinfo_next(child);
3807 outfile = procmime_get_tmp_file_name(child);
3808 if ((err = procmime_get_part(outfile, child)) < 0)
3809 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3811 gchar *content_type;
3813 content_type = procmime_get_content_type_str(child->type, child->subtype);
3815 /* if we meet a pgp signature, we don't attach it, but
3816 * we force signing. */
3817 if ((strcmp(content_type, "application/pgp-signature") &&
3818 strcmp(content_type, "application/pkcs7-signature") &&
3819 strcmp(content_type, "application/x-pkcs7-signature"))
3820 || compose->mode == COMPOSE_REDIRECT) {
3821 partname = procmime_mimeinfo_get_parameter(child, "filename");
3822 if (partname == NULL)
3823 partname = procmime_mimeinfo_get_parameter(child, "name");
3824 if (partname == NULL)
3826 compose_attach_append(compose, outfile,
3827 partname, content_type,
3828 procmime_mimeinfo_get_parameter(child, "charset"));
3830 compose_force_signing(compose, compose->account, NULL);
3832 g_free(content_type);
3835 NEXT_PART_NOT_CHILD(child);
3837 procmime_mimeinfo_free_all(mimeinfo);
3840 #undef NEXT_PART_NOT_CHILD
3845 WAIT_FOR_INDENT_CHAR,
3846 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3849 /* return indent length, we allow:
3850 indent characters followed by indent characters or spaces/tabs,
3851 alphabets and numbers immediately followed by indent characters,
3852 and the repeating sequences of the above
3853 If quote ends with multiple spaces, only the first one is included. */
3854 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3855 const GtkTextIter *start, gint *len)
3857 GtkTextIter iter = *start;
3861 IndentState state = WAIT_FOR_INDENT_CHAR;
3864 gint alnum_count = 0;
3865 gint space_count = 0;
3868 if (prefs_common.quote_chars == NULL) {
3872 while (!gtk_text_iter_ends_line(&iter)) {
3873 wc = gtk_text_iter_get_char(&iter);
3874 if (g_unichar_iswide(wc))
3876 clen = g_unichar_to_utf8(wc, ch);
3880 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3881 is_space = g_unichar_isspace(wc);
3883 if (state == WAIT_FOR_INDENT_CHAR) {
3884 if (!is_indent && !g_unichar_isalnum(wc))
3887 quote_len += alnum_count + space_count + 1;
3888 alnum_count = space_count = 0;
3889 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3892 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3893 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3897 else if (is_indent) {
3898 quote_len += alnum_count + space_count + 1;
3899 alnum_count = space_count = 0;
3902 state = WAIT_FOR_INDENT_CHAR;
3906 gtk_text_iter_forward_char(&iter);
3909 if (quote_len > 0 && space_count > 0)
3915 if (quote_len > 0) {
3917 gtk_text_iter_forward_chars(&iter, quote_len);
3918 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3924 /* return >0 if the line is itemized */
3925 static int compose_itemized_length(GtkTextBuffer *buffer,
3926 const GtkTextIter *start)
3928 GtkTextIter iter = *start;
3933 if (gtk_text_iter_ends_line(&iter))
3938 wc = gtk_text_iter_get_char(&iter);
3939 if (!g_unichar_isspace(wc))
3941 gtk_text_iter_forward_char(&iter);
3942 if (gtk_text_iter_ends_line(&iter))
3946 clen = g_unichar_to_utf8(wc, ch);
3950 if (!strchr("*-+", ch[0]))
3953 gtk_text_iter_forward_char(&iter);
3954 if (gtk_text_iter_ends_line(&iter))
3956 wc = gtk_text_iter_get_char(&iter);
3957 if (g_unichar_isspace(wc)) {
3963 /* return the string at the start of the itemization */
3964 static gchar * compose_get_itemized_chars(GtkTextBuffer *buffer,
3965 const GtkTextIter *start)
3967 GtkTextIter iter = *start;
3970 GString *item_chars = g_string_new("");
3973 if (gtk_text_iter_ends_line(&iter))
3978 wc = gtk_text_iter_get_char(&iter);
3979 if (!g_unichar_isspace(wc))
3981 gtk_text_iter_forward_char(&iter);
3982 if (gtk_text_iter_ends_line(&iter))
3984 g_string_append_unichar(item_chars, wc);
3987 str = item_chars->str;
3988 g_string_free(item_chars, FALSE);
3992 /* return the number of spaces at a line's start */
3993 static int compose_left_offset_length(GtkTextBuffer *buffer,
3994 const GtkTextIter *start)
3996 GtkTextIter iter = *start;
3999 if (gtk_text_iter_ends_line(&iter))
4003 wc = gtk_text_iter_get_char(&iter);
4004 if (!g_unichar_isspace(wc))
4007 gtk_text_iter_forward_char(&iter);
4008 if (gtk_text_iter_ends_line(&iter))
4012 gtk_text_iter_forward_char(&iter);
4013 if (gtk_text_iter_ends_line(&iter))
4018 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
4019 const GtkTextIter *start,
4020 GtkTextIter *break_pos,
4024 GtkTextIter iter = *start, line_end = *start;
4025 PangoLogAttr *attrs;
4032 gboolean can_break = FALSE;
4033 gboolean do_break = FALSE;
4034 gboolean was_white = FALSE;
4035 gboolean prev_dont_break = FALSE;
4037 gtk_text_iter_forward_to_line_end(&line_end);
4038 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
4039 len = g_utf8_strlen(str, -1);
4043 g_warning("compose_get_line_break_pos: len = 0!\n");
4047 /* g_print("breaking line: %d: %s (len = %d)\n",
4048 gtk_text_iter_get_line(&iter), str, len); */
4050 attrs = g_new(PangoLogAttr, len + 1);
4052 pango_default_break(str, -1, NULL, attrs, len + 1);
4056 /* skip quote and leading spaces */
4057 for (i = 0; *p != '\0' && i < len; i++) {
4060 wc = g_utf8_get_char(p);
4061 if (i >= quote_len && !g_unichar_isspace(wc))
4063 if (g_unichar_iswide(wc))
4065 else if (*p == '\t')
4069 p = g_utf8_next_char(p);
4072 for (; *p != '\0' && i < len; i++) {
4073 PangoLogAttr *attr = attrs + i;
4077 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
4080 was_white = attr->is_white;
4082 /* don't wrap URI */
4083 if ((uri_len = get_uri_len(p)) > 0) {
4085 if (pos > 0 && col > max_col) {
4095 wc = g_utf8_get_char(p);
4096 if (g_unichar_iswide(wc)) {
4098 if (prev_dont_break && can_break && attr->is_line_break)
4100 } else if (*p == '\t')
4104 if (pos > 0 && col > max_col) {
4109 if (*p == '-' || *p == '/')
4110 prev_dont_break = TRUE;
4112 prev_dont_break = FALSE;
4114 p = g_utf8_next_char(p);
4118 // debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
4123 *break_pos = *start;
4124 gtk_text_iter_set_line_offset(break_pos, pos);
4129 static gboolean compose_join_next_line(Compose *compose,
4130 GtkTextBuffer *buffer,
4132 const gchar *quote_str)
4134 GtkTextIter iter_ = *iter, cur, prev, next, end;
4135 PangoLogAttr attrs[3];
4137 gchar *next_quote_str;
4140 gboolean keep_cursor = FALSE;
4142 if (!gtk_text_iter_forward_line(&iter_) ||
4143 gtk_text_iter_ends_line(&iter_)) {
4146 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
4148 if ((quote_str || next_quote_str) &&
4149 strcmp2(quote_str, next_quote_str) != 0) {
4150 g_free(next_quote_str);
4153 g_free(next_quote_str);
4156 if (quote_len > 0) {
4157 gtk_text_iter_forward_chars(&end, quote_len);
4158 if (gtk_text_iter_ends_line(&end)) {
4163 /* don't join itemized lines */
4164 if (compose_itemized_length(buffer, &end) > 0) {
4168 /* don't join signature separator */
4169 if (compose_is_sig_separator(compose, buffer, &iter_)) {
4172 /* delete quote str */
4174 gtk_text_buffer_delete(buffer, &iter_, &end);
4176 /* don't join line breaks put by the user */
4178 gtk_text_iter_backward_char(&cur);
4179 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
4180 gtk_text_iter_forward_char(&cur);
4184 gtk_text_iter_forward_char(&cur);
4185 /* delete linebreak and extra spaces */
4186 while (gtk_text_iter_backward_char(&cur)) {
4187 wc1 = gtk_text_iter_get_char(&cur);
4188 if (!g_unichar_isspace(wc1))
4193 while (!gtk_text_iter_ends_line(&cur)) {
4194 wc1 = gtk_text_iter_get_char(&cur);
4195 if (!g_unichar_isspace(wc1))
4197 gtk_text_iter_forward_char(&cur);
4200 if (!gtk_text_iter_equal(&prev, &next)) {
4203 mark = gtk_text_buffer_get_insert(buffer);
4204 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4205 if (gtk_text_iter_equal(&prev, &cur))
4207 gtk_text_buffer_delete(buffer, &prev, &next);
4211 /* insert space if required */
4212 gtk_text_iter_backward_char(&prev);
4213 wc1 = gtk_text_iter_get_char(&prev);
4214 wc2 = gtk_text_iter_get_char(&next);
4215 gtk_text_iter_forward_char(&next);
4216 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4217 pango_default_break(str, -1, NULL, attrs, 3);
4218 if (!attrs[1].is_line_break ||
4219 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4220 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4222 gtk_text_iter_backward_char(&iter_);
4223 gtk_text_buffer_place_cursor(buffer, &iter_);
4232 #define ADD_TXT_POS(bp_, ep_, pti_) \
4233 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4234 last = last->next; \
4235 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4236 last->next = NULL; \
4238 g_warning("alloc error scanning URIs\n"); \
4241 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4243 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4244 GtkTextBuffer *buffer;
4245 GtkTextIter iter, break_pos, end_of_line;
4246 gchar *quote_str = NULL;
4248 gboolean wrap_quote = prefs_common.linewrap_quote;
4249 gboolean prev_autowrap = compose->autowrap;
4250 gint startq_offset = -1, noq_offset = -1;
4251 gint uri_start = -1, uri_stop = -1;
4252 gint nouri_start = -1, nouri_stop = -1;
4253 gint num_blocks = 0;
4254 gint quotelevel = -1;
4255 gboolean modified = force;
4256 gboolean removed = FALSE;
4257 gboolean modified_before_remove = FALSE;
4259 gboolean start = TRUE;
4260 gint itemized_len = 0, rem_item_len = 0;
4261 gchar *itemized_chars = NULL;
4262 gboolean item_continuation = FALSE;
4267 if (compose->draft_timeout_tag == -2) {
4271 compose->autowrap = FALSE;
4273 buffer = gtk_text_view_get_buffer(text);
4274 undo_wrapping(compose->undostruct, TRUE);
4279 mark = gtk_text_buffer_get_insert(buffer);
4280 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4284 if (compose->draft_timeout_tag == -2) {
4285 if (gtk_text_iter_ends_line(&iter)) {
4286 while (gtk_text_iter_ends_line(&iter) &&
4287 gtk_text_iter_forward_line(&iter))
4290 while (gtk_text_iter_backward_line(&iter)) {
4291 if (gtk_text_iter_ends_line(&iter)) {
4292 gtk_text_iter_forward_line(&iter);
4298 /* move to line start */
4299 gtk_text_iter_set_line_offset(&iter, 0);
4302 itemized_len = compose_itemized_length(buffer, &iter);
4304 if (!itemized_len) {
4305 itemized_len = compose_left_offset_length(buffer, &iter);
4306 item_continuation = TRUE;
4310 itemized_chars = compose_get_itemized_chars(buffer, &iter);
4312 /* go until paragraph end (empty line) */
4313 while (start || !gtk_text_iter_ends_line(&iter)) {
4314 gchar *scanpos = NULL;
4315 /* parse table - in order of priority */
4317 const gchar *needle; /* token */
4319 /* token search function */
4320 gchar *(*search) (const gchar *haystack,
4321 const gchar *needle);
4322 /* part parsing function */
4323 gboolean (*parse) (const gchar *start,
4324 const gchar *scanpos,
4328 /* part to URI function */
4329 gchar *(*build_uri) (const gchar *bp,
4333 static struct table parser[] = {
4334 {"http://", strcasestr, get_uri_part, make_uri_string},
4335 {"https://", strcasestr, get_uri_part, make_uri_string},
4336 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4337 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4338 {"gopher://",strcasestr, get_uri_part, make_uri_string},
4339 {"www.", strcasestr, get_uri_part, make_http_string},
4340 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4341 {"@", strcasestr, get_email_part, make_email_string}
4343 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4344 gint last_index = PARSE_ELEMS;
4346 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4350 if (!prev_autowrap && num_blocks == 0) {
4352 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4353 G_CALLBACK(text_inserted),
4356 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4359 uri_start = uri_stop = -1;
4361 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4364 // debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4365 if (startq_offset == -1)
4366 startq_offset = gtk_text_iter_get_offset(&iter);
4367 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4368 if (quotelevel > 2) {
4369 /* recycle colors */
4370 if (prefs_common.recycle_quote_colors)
4379 if (startq_offset == -1)
4380 noq_offset = gtk_text_iter_get_offset(&iter);
4384 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4387 if (gtk_text_iter_ends_line(&iter)) {
4389 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4390 prefs_common.linewrap_len,
4392 GtkTextIter prev, next, cur;
4393 if (prev_autowrap != FALSE || force) {
4394 compose->automatic_break = TRUE;
4396 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4397 compose->automatic_break = FALSE;
4398 if (itemized_len && compose->autoindent) {
4399 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4400 if (!item_continuation)
4401 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4403 } else if (quote_str && wrap_quote) {
4404 compose->automatic_break = TRUE;
4406 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4407 compose->automatic_break = FALSE;
4408 if (itemized_len && compose->autoindent) {
4409 gtk_text_buffer_insert(buffer, &break_pos, itemized_chars, -1);
4410 if (!item_continuation)
4411 gtk_text_buffer_insert(buffer, &break_pos, " ", 2);
4415 /* remove trailing spaces */
4417 rem_item_len = itemized_len;
4418 while (compose->autoindent && rem_item_len-- > 0)
4419 gtk_text_iter_backward_char(&cur);
4420 gtk_text_iter_backward_char(&cur);
4423 while (!gtk_text_iter_starts_line(&cur)) {
4426 gtk_text_iter_backward_char(&cur);
4427 wc = gtk_text_iter_get_char(&cur);
4428 if (!g_unichar_isspace(wc))
4432 if (!gtk_text_iter_equal(&prev, &next)) {
4433 gtk_text_buffer_delete(buffer, &prev, &next);
4435 gtk_text_iter_forward_char(&break_pos);
4439 gtk_text_buffer_insert(buffer, &break_pos,
4443 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4445 /* move iter to current line start */
4446 gtk_text_iter_set_line_offset(&iter, 0);
4453 /* move iter to next line start */
4459 if (!prev_autowrap && num_blocks > 0) {
4461 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4462 G_CALLBACK(text_inserted),
4466 while (!gtk_text_iter_ends_line(&end_of_line)) {
4467 gtk_text_iter_forward_char(&end_of_line);
4469 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4471 nouri_start = gtk_text_iter_get_offset(&iter);
4472 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4474 walk_pos = gtk_text_iter_get_offset(&iter);
4475 /* FIXME: this looks phony. scanning for anything in the parse table */
4476 for (n = 0; n < PARSE_ELEMS; n++) {
4479 tmp = parser[n].search(walk, parser[n].needle);
4481 if (scanpos == NULL || tmp < scanpos) {
4490 /* check if URI can be parsed */
4491 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4492 (const gchar **)&ep, FALSE)
4493 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4497 strlen(parser[last_index].needle);
4500 uri_start = walk_pos + (bp - o_walk);
4501 uri_stop = walk_pos + (ep - o_walk);
4505 gtk_text_iter_forward_line(&iter);
4508 if (startq_offset != -1) {
4509 GtkTextIter startquote, endquote;
4510 gtk_text_buffer_get_iter_at_offset(
4511 buffer, &startquote, startq_offset);
4514 switch (quotelevel) {
4516 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4517 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4518 gtk_text_buffer_apply_tag_by_name(
4519 buffer, "quote0", &startquote, &endquote);
4520 gtk_text_buffer_remove_tag_by_name(
4521 buffer, "quote1", &startquote, &endquote);
4522 gtk_text_buffer_remove_tag_by_name(
4523 buffer, "quote2", &startquote, &endquote);
4528 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4529 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4530 gtk_text_buffer_apply_tag_by_name(
4531 buffer, "quote1", &startquote, &endquote);
4532 gtk_text_buffer_remove_tag_by_name(
4533 buffer, "quote0", &startquote, &endquote);
4534 gtk_text_buffer_remove_tag_by_name(
4535 buffer, "quote2", &startquote, &endquote);
4540 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4541 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4542 gtk_text_buffer_apply_tag_by_name(
4543 buffer, "quote2", &startquote, &endquote);
4544 gtk_text_buffer_remove_tag_by_name(
4545 buffer, "quote0", &startquote, &endquote);
4546 gtk_text_buffer_remove_tag_by_name(
4547 buffer, "quote1", &startquote, &endquote);
4553 } else if (noq_offset != -1) {
4554 GtkTextIter startnoquote, endnoquote;
4555 gtk_text_buffer_get_iter_at_offset(
4556 buffer, &startnoquote, noq_offset);
4559 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4560 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4561 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4562 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4563 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4564 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4565 gtk_text_buffer_remove_tag_by_name(
4566 buffer, "quote0", &startnoquote, &endnoquote);
4567 gtk_text_buffer_remove_tag_by_name(
4568 buffer, "quote1", &startnoquote, &endnoquote);
4569 gtk_text_buffer_remove_tag_by_name(
4570 buffer, "quote2", &startnoquote, &endnoquote);
4576 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4577 GtkTextIter nouri_start_iter, nouri_end_iter;
4578 gtk_text_buffer_get_iter_at_offset(
4579 buffer, &nouri_start_iter, nouri_start);
4580 gtk_text_buffer_get_iter_at_offset(
4581 buffer, &nouri_end_iter, nouri_stop);
4582 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4583 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4584 gtk_text_buffer_remove_tag_by_name(
4585 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4586 modified_before_remove = modified;
4591 if (uri_start >= 0 && uri_stop > 0) {
4592 GtkTextIter uri_start_iter, uri_end_iter, back;
4593 gtk_text_buffer_get_iter_at_offset(
4594 buffer, &uri_start_iter, uri_start);
4595 gtk_text_buffer_get_iter_at_offset(
4596 buffer, &uri_end_iter, uri_stop);
4597 back = uri_end_iter;
4598 gtk_text_iter_backward_char(&back);
4599 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4600 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4601 gtk_text_buffer_apply_tag_by_name(
4602 buffer, "link", &uri_start_iter, &uri_end_iter);
4604 if (removed && !modified_before_remove) {
4610 // debug_print("not modified, out after %d lines\n", lines);
4614 // debug_print("modified, out after %d lines\n", lines);
4616 g_free(itemized_chars);
4619 undo_wrapping(compose->undostruct, FALSE);
4620 compose->autowrap = prev_autowrap;
4625 void compose_action_cb(void *data)
4627 Compose *compose = (Compose *)data;
4628 compose_wrap_all(compose);
4631 static void compose_wrap_all(Compose *compose)
4633 compose_wrap_all_full(compose, FALSE);
4636 static void compose_wrap_all_full(Compose *compose, gboolean force)
4638 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4639 GtkTextBuffer *buffer;
4641 gboolean modified = TRUE;
4643 buffer = gtk_text_view_get_buffer(text);
4645 gtk_text_buffer_get_start_iter(buffer, &iter);
4646 while (!gtk_text_iter_is_end(&iter) && modified)
4647 modified = compose_beautify_paragraph(compose, &iter, force);
4651 static void compose_set_title(Compose *compose)
4657 edited = compose->modified ? _(" [Edited]") : "";
4659 subject = gtk_editable_get_chars(
4660 GTK_EDITABLE(compose->subject_entry), 0, -1);
4662 #ifndef GENERIC_UMPC
4663 if (subject && strlen(subject))
4664 str = g_strdup_printf(_("%s - Compose message%s"),
4667 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4669 str = g_strdup(_("Compose message"));
4672 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4678 * compose_current_mail_account:
4680 * Find a current mail account (the currently selected account, or the
4681 * default account, if a news account is currently selected). If a
4682 * mail account cannot be found, display an error message.
4684 * Return value: Mail account, or NULL if not found.
4686 static PrefsAccount *
4687 compose_current_mail_account(void)
4691 if (cur_account && cur_account->protocol != A_NNTP)
4694 ac = account_get_default();
4695 if (!ac || ac->protocol == A_NNTP) {
4696 alertpanel_error(_("Account for sending mail is not specified.\n"
4697 "Please select a mail account before sending."));
4704 #define QUOTE_IF_REQUIRED(out, str) \
4706 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4710 len = strlen(str) + 3; \
4711 if ((__tmp = alloca(len)) == NULL) { \
4712 g_warning("can't allocate memory\n"); \
4713 g_string_free(header, TRUE); \
4716 g_snprintf(__tmp, len, "\"%s\"", str); \
4721 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4722 g_warning("can't allocate memory\n"); \
4723 g_string_free(header, TRUE); \
4726 strcpy(__tmp, str); \
4732 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4734 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4738 len = strlen(str) + 3; \
4739 if ((__tmp = alloca(len)) == NULL) { \
4740 g_warning("can't allocate memory\n"); \
4743 g_snprintf(__tmp, len, "\"%s\"", str); \
4748 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4749 g_warning("can't allocate memory\n"); \
4752 strcpy(__tmp, str); \
4758 static void compose_select_account(Compose *compose, PrefsAccount *account,
4761 gchar *from = NULL, *header = NULL;
4762 ComposeHeaderEntry *header_entry;
4763 #if GTK_CHECK_VERSION(2, 24, 0)
4767 cm_return_if_fail(account != NULL);
4769 compose->account = account;
4770 if (account->name && *account->name) {
4772 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4773 from = g_strdup_printf("%s <%s>",
4774 buf, account->address);
4775 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4777 from = g_strdup_printf("<%s>",
4779 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4784 compose_set_title(compose);
4786 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4787 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", TRUE);
4789 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Sign", FALSE);
4790 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4791 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", TRUE);
4793 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Options/Encrypt", FALSE);
4795 activate_privacy_system(compose, account, FALSE);
4797 if (!init && compose->mode != COMPOSE_REDIRECT) {
4798 undo_block(compose->undostruct);
4799 compose_insert_sig(compose, TRUE);
4800 undo_unblock(compose->undostruct);
4803 header_entry = (ComposeHeaderEntry *) compose->header_list->data;
4804 #if !GTK_CHECK_VERSION(2, 24, 0)
4805 header = gtk_combo_box_get_active_text(GTK_COMBO_BOX(header_entry->combo));
4807 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(header_entry->combo), &iter))
4808 gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(
4809 header_entry->combo)), &iter, COMBOBOX_TEXT, &header, -1);
4812 if (header && !strlen(gtk_entry_get_text(GTK_ENTRY(header_entry->entry)))) {
4813 if (account->protocol == A_NNTP) {
4814 if (!strcmp(header, _("To:")))
4815 combobox_select_by_text(
4816 GTK_COMBO_BOX(header_entry->combo),
4819 if (!strcmp(header, _("Newsgroups:")))
4820 combobox_select_by_text(
4821 GTK_COMBO_BOX(header_entry->combo),
4829 /* use account's dict info if set */
4830 if (compose->gtkaspell) {
4831 if (account->enable_default_dictionary)
4832 gtkaspell_change_dict(compose->gtkaspell,
4833 account->default_dictionary, FALSE);
4834 if (account->enable_default_alt_dictionary)
4835 gtkaspell_change_alt_dict(compose->gtkaspell,
4836 account->default_alt_dictionary);
4837 if (account->enable_default_dictionary
4838 || account->enable_default_alt_dictionary)
4839 compose_spell_menu_changed(compose);
4844 gboolean compose_check_for_valid_recipient(Compose *compose) {
4845 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4846 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4847 gboolean recipient_found = FALSE;
4851 /* free to and newsgroup list */
4852 slist_free_strings_full(compose->to_list);
4853 compose->to_list = NULL;
4855 slist_free_strings_full(compose->newsgroup_list);
4856 compose->newsgroup_list = NULL;
4858 /* search header entries for to and newsgroup entries */
4859 for (list = compose->header_list; list; list = list->next) {
4862 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4863 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4866 if (entry[0] != '\0') {
4867 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4868 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4869 compose->to_list = address_list_append(compose->to_list, entry);
4870 recipient_found = TRUE;
4873 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4874 if (!g_ascii_strcasecmp(header, prefs_common_translated_header_name(*strptr))) {
4875 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4876 recipient_found = TRUE;
4883 return recipient_found;
4886 static gboolean compose_check_for_set_recipients(Compose *compose)
4888 if (compose->account->set_autocc && compose->account->auto_cc) {
4889 gboolean found_other = FALSE;
4891 /* search header entries for to and newsgroup entries */
4892 for (list = compose->header_list; list; list = list->next) {
4895 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4896 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4899 if (strcmp(entry, compose->account->auto_cc)
4900 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4910 if (compose->batch) {
4911 gtk_widget_show_all(compose->window);
4913 aval = alertpanel(_("Send"),
4914 _("The only recipient is the default CC address. Send anyway?"),
4915 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4916 if (aval != G_ALERTALTERNATE)
4920 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4921 gboolean found_other = FALSE;
4923 /* search header entries for to and newsgroup entries */
4924 for (list = compose->header_list; list; list = list->next) {
4927 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4928 header = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
4931 if (strcmp(entry, compose->account->auto_bcc)
4932 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4942 if (compose->batch) {
4943 gtk_widget_show_all(compose->window);
4945 aval = alertpanel(_("Send"),
4946 _("The only recipient is the default BCC address. Send anyway?"),
4947 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4948 if (aval != G_ALERTALTERNATE)
4955 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4959 if (compose_check_for_valid_recipient(compose) == FALSE) {
4960 if (compose->batch) {
4961 gtk_widget_show_all(compose->window);
4963 alertpanel_error(_("Recipient is not specified."));
4967 if (compose_check_for_set_recipients(compose) == FALSE) {
4971 if (!compose->batch) {
4972 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4973 if (*str == '\0' && check_everything == TRUE &&
4974 compose->mode != COMPOSE_REDIRECT) {
4976 gchar *button_label;
4979 if (compose->sending)
4980 button_label = _("+_Send");
4982 button_label = _("+_Queue");
4983 message = g_strdup_printf(_("Subject is empty. %s"),
4984 compose->sending?_("Send it anyway?"):
4985 _("Queue it anyway?"));
4987 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4988 GTK_STOCK_CANCEL, button_label, NULL);
4990 if (aval != G_ALERTALTERNATE)
4995 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
5001 gint compose_send(Compose *compose)
5004 FolderItem *folder = NULL;
5006 gchar *msgpath = NULL;
5007 gboolean discard_window = FALSE;
5008 gchar *errstr = NULL;
5009 gchar *tmsgid = NULL;
5010 MainWindow *mainwin = mainwindow_get_mainwindow();
5011 gboolean queued_removed = FALSE;
5013 if (prefs_common.send_dialog_invisible
5014 || compose->batch == TRUE)
5015 discard_window = TRUE;
5017 compose_allow_user_actions (compose, FALSE);
5018 compose->sending = TRUE;
5020 if (compose_check_entries(compose, TRUE) == FALSE) {
5021 if (compose->batch) {
5022 gtk_widget_show_all(compose->window);
5028 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
5031 if (compose->batch) {
5032 gtk_widget_show_all(compose->window);
5035 alertpanel_error(_("Could not queue message for sending:\n\n"
5036 "Charset conversion failed."));
5037 } else if (val == -5) {
5038 alertpanel_error(_("Could not queue message for sending:\n\n"
5039 "Couldn't get recipient encryption key."));
5040 } else if (val == -6) {
5042 } else if (val == -3) {
5043 if (privacy_peek_error())
5044 alertpanel_error(_("Could not queue message for sending:\n\n"
5045 "Signature failed: %s"), privacy_get_error());
5046 } else if (val == -2 && errno != 0) {
5047 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
5049 alertpanel_error(_("Could not queue message for sending."));
5054 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
5055 if (discard_window) {
5056 compose->sending = FALSE;
5057 compose_close(compose);
5058 /* No more compose access in the normal codepath
5059 * after this point! */
5064 alertpanel_error(_("The message was queued but could not be "
5065 "sent.\nUse \"Send queued messages\" from "
5066 "the main window to retry."));
5067 if (!discard_window) {
5074 if (msgpath == NULL) {
5075 msgpath = folder_item_fetch_msg(folder, msgnum);
5076 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5079 val = procmsg_send_message_queue_with_lock(msgpath, &errstr, folder, msgnum, &queued_removed);
5080 claws_unlink(msgpath);
5083 if (!discard_window) {
5085 if (!queued_removed)
5086 folder_item_remove_msg(folder, msgnum);
5087 folder_item_scan(folder);
5089 /* make sure we delete that */
5090 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5092 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5093 folder_item_remove_msg(folder, tmp->msgnum);
5094 procmsg_msginfo_free(tmp);
5101 if (!queued_removed)
5102 folder_item_remove_msg(folder, msgnum);
5103 folder_item_scan(folder);
5105 /* make sure we delete that */
5106 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
5108 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
5109 folder_item_remove_msg(folder, tmp->msgnum);
5110 procmsg_msginfo_free(tmp);
5113 if (!discard_window) {
5114 compose->sending = FALSE;
5115 compose_allow_user_actions (compose, TRUE);
5116 compose_close(compose);
5120 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
5121 "the main window to retry."), errstr);
5124 alertpanel_error_log(_("The message was queued but could not be "
5125 "sent.\nUse \"Send queued messages\" from "
5126 "the main window to retry."));
5128 if (!discard_window) {
5137 toolbar_main_set_sensitive(mainwin);
5138 main_window_set_menu_sensitive(mainwin);
5144 compose_allow_user_actions (compose, TRUE);
5145 compose->sending = FALSE;
5146 compose->modified = TRUE;
5147 toolbar_main_set_sensitive(mainwin);
5148 main_window_set_menu_sensitive(mainwin);
5153 static gboolean compose_use_attach(Compose *compose)
5155 GtkTreeModel *model = gtk_tree_view_get_model
5156 (GTK_TREE_VIEW(compose->attach_clist));
5157 return gtk_tree_model_iter_n_children(model, NULL) > 0;
5160 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
5163 gchar buf[BUFFSIZE];
5165 gboolean first_to_address;
5166 gboolean first_cc_address;
5168 ComposeHeaderEntry *headerentry;
5169 const gchar *headerentryname;
5170 const gchar *cc_hdr;
5171 const gchar *to_hdr;
5172 gboolean err = FALSE;
5174 debug_print("Writing redirect header\n");
5176 cc_hdr = prefs_common_translated_header_name("Cc:");
5177 to_hdr = prefs_common_translated_header_name("To:");
5179 first_to_address = TRUE;
5180 for (list = compose->header_list; list; list = list->next) {
5181 headerentry = ((ComposeHeaderEntry *)list->data);
5182 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5184 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
5185 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5186 Xstrdup_a(str, entstr, return -1);
5188 if (str[0] != '\0') {
5189 compose_convert_header
5190 (compose, buf, sizeof(buf), str,
5191 strlen("Resent-To") + 2, TRUE);
5193 if (first_to_address) {
5194 err |= (fprintf(fp, "Resent-To: ") < 0);
5195 first_to_address = FALSE;
5197 err |= (fprintf(fp, ",") < 0);
5199 err |= (fprintf(fp, "%s", buf) < 0);
5203 if (!first_to_address) {
5204 err |= (fprintf(fp, "\n") < 0);
5207 first_cc_address = TRUE;
5208 for (list = compose->header_list; list; list = list->next) {
5209 headerentry = ((ComposeHeaderEntry *)list->data);
5210 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
5212 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
5213 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
5214 Xstrdup_a(str, strg, return -1);
5216 if (str[0] != '\0') {
5217 compose_convert_header
5218 (compose, buf, sizeof(buf), str,
5219 strlen("Resent-Cc") + 2, TRUE);
5221 if (first_cc_address) {
5222 err |= (fprintf(fp, "Resent-Cc: ") < 0);
5223 first_cc_address = FALSE;
5225 err |= (fprintf(fp, ",") < 0);
5227 err |= (fprintf(fp, "%s", buf) < 0);
5231 if (!first_cc_address) {
5232 err |= (fprintf(fp, "\n") < 0);
5235 return (err ? -1:0);
5238 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
5240 gchar buf[BUFFSIZE];
5242 const gchar *entstr;
5243 /* struct utsname utsbuf; */
5244 gboolean err = FALSE;
5246 cm_return_val_if_fail(fp != NULL, -1);
5247 cm_return_val_if_fail(compose->account != NULL, -1);
5248 cm_return_val_if_fail(compose->account->address != NULL, -1);
5251 get_rfc822_date(buf, sizeof(buf));
5252 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5255 if (compose->account->name && *compose->account->name) {
5256 compose_convert_header
5257 (compose, buf, sizeof(buf), compose->account->name,
5258 strlen("From: "), TRUE);
5259 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5260 buf, compose->account->address) < 0);
5262 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5265 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5266 if (*entstr != '\0') {
5267 Xstrdup_a(str, entstr, return -1);
5270 compose_convert_header(compose, buf, sizeof(buf), str,
5271 strlen("Subject: "), FALSE);
5272 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5276 /* Resent-Message-ID */
5277 if (compose->account->set_domain && compose->account->domain) {
5278 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5279 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5280 g_snprintf(buf, sizeof(buf), "%s",
5281 strchr(compose->account->address, '@') ?
5282 strchr(compose->account->address, '@')+1 :
5283 compose->account->address);
5285 g_snprintf(buf, sizeof(buf), "%s", "");
5288 if (compose->account->gen_msgid) {
5290 if (compose->account->msgid_with_addr) {
5291 addr = compose->account->address;
5293 generate_msgid(buf, sizeof(buf), addr);
5294 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5295 compose->msgid = g_strdup(buf);
5297 compose->msgid = NULL;
5300 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5303 /* separator between header and body */
5304 err |= (fputs("\n", fp) == EOF);
5306 return (err ? -1:0);
5309 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5313 gchar buf[BUFFSIZE];
5315 gboolean skip = FALSE;
5316 gboolean err = FALSE;
5317 gchar *not_included[]={
5318 "Return-Path:", "Delivered-To:", "Received:",
5319 "Subject:", "X-UIDL:", "AF:",
5320 "NF:", "PS:", "SRH:",
5321 "SFN:", "DSR:", "MID:",
5322 "CFG:", "PT:", "S:",
5323 "RQ:", "SSV:", "NSV:",
5324 "SSH:", "R:", "MAID:",
5325 "NAID:", "RMID:", "FMID:",
5326 "SCF:", "RRCPT:", "NG:",
5327 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5328 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5329 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5330 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5331 "X-Claws-Auto-Wrapping:", "X-Claws-Auto-Indent:",
5334 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5335 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5339 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5341 for (i = 0; not_included[i] != NULL; i++) {
5342 if (g_ascii_strncasecmp(buf, not_included[i],
5343 strlen(not_included[i])) == 0) {
5350 if (fputs(buf, fdest) == -1)
5353 if (!prefs_common.redirect_keep_from) {
5354 if (g_ascii_strncasecmp(buf, "From:",
5355 strlen("From:")) == 0) {
5356 err |= (fputs(" (by way of ", fdest) == EOF);
5357 if (compose->account->name
5358 && *compose->account->name) {
5359 compose_convert_header
5360 (compose, buf, sizeof(buf),
5361 compose->account->name,
5364 err |= (fprintf(fdest, "%s <%s>",
5366 compose->account->address) < 0);
5368 err |= (fprintf(fdest, "%s",
5369 compose->account->address) < 0);
5370 err |= (fputs(")", fdest) == EOF);
5374 if (fputs("\n", fdest) == -1)
5381 if (compose_redirect_write_headers(compose, fdest))
5384 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5385 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5398 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5400 GtkTextBuffer *buffer;
5401 GtkTextIter start, end;
5404 const gchar *out_codeset;
5405 EncodingType encoding = ENC_UNKNOWN;
5406 MimeInfo *mimemsg, *mimetext;
5408 const gchar *src_codeset = CS_INTERNAL;
5409 gchar *from_addr = NULL;
5410 gchar *from_name = NULL;
5412 if (action == COMPOSE_WRITE_FOR_SEND)
5413 attach_parts = TRUE;
5415 /* create message MimeInfo */
5416 mimemsg = procmime_mimeinfo_new();
5417 mimemsg->type = MIMETYPE_MESSAGE;
5418 mimemsg->subtype = g_strdup("rfc822");
5419 mimemsg->content = MIMECONTENT_MEM;
5420 mimemsg->tmp = TRUE; /* must free content later */
5421 mimemsg->data.mem = compose_get_header(compose);
5423 /* Create text part MimeInfo */
5424 /* get all composed text */
5425 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5426 gtk_text_buffer_get_start_iter(buffer, &start);
5427 gtk_text_buffer_get_end_iter(buffer, &end);
5428 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5430 out_codeset = conv_get_charset_str(compose->out_encoding);
5432 if (!out_codeset && is_ascii_str(chars)) {
5433 out_codeset = CS_US_ASCII;
5434 } else if (prefs_common.outgoing_fallback_to_ascii &&
5435 is_ascii_str(chars)) {
5436 out_codeset = CS_US_ASCII;
5437 encoding = ENC_7BIT;
5441 gchar *test_conv_global_out = NULL;
5442 gchar *test_conv_reply = NULL;
5444 /* automatic mode. be automatic. */
5445 codeconv_set_strict(TRUE);
5447 out_codeset = conv_get_outgoing_charset_str();
5449 debug_print("trying to convert to %s\n", out_codeset);
5450 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5453 if (!test_conv_global_out && compose->orig_charset
5454 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5455 out_codeset = compose->orig_charset;
5456 debug_print("failure; trying to convert to %s\n", out_codeset);
5457 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5460 if (!test_conv_global_out && !test_conv_reply) {
5462 out_codeset = CS_INTERNAL;
5463 debug_print("failure; finally using %s\n", out_codeset);
5465 g_free(test_conv_global_out);
5466 g_free(test_conv_reply);
5467 codeconv_set_strict(FALSE);
5470 if (encoding == ENC_UNKNOWN) {
5471 if (prefs_common.encoding_method == CTE_BASE64)
5472 encoding = ENC_BASE64;
5473 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5474 encoding = ENC_QUOTED_PRINTABLE;
5475 else if (prefs_common.encoding_method == CTE_8BIT)
5476 encoding = ENC_8BIT;
5478 encoding = procmime_get_encoding_for_charset(out_codeset);
5481 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5482 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5484 if (action == COMPOSE_WRITE_FOR_SEND) {
5485 codeconv_set_strict(TRUE);
5486 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5487 codeconv_set_strict(FALSE);
5493 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5494 "to the specified %s charset.\n"
5495 "Send it as %s?"), out_codeset, src_codeset);
5496 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5497 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5500 if (aval != G_ALERTALTERNATE) {
5505 out_codeset = src_codeset;
5511 out_codeset = src_codeset;
5516 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5517 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5518 strstr(buf, "\nFrom ") != NULL) {
5519 encoding = ENC_QUOTED_PRINTABLE;
5523 mimetext = procmime_mimeinfo_new();
5524 mimetext->content = MIMECONTENT_MEM;
5525 mimetext->tmp = TRUE; /* must free content later */
5526 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5527 * and free the data, which we need later. */
5528 mimetext->data.mem = g_strdup(buf);
5529 mimetext->type = MIMETYPE_TEXT;
5530 mimetext->subtype = g_strdup("plain");
5531 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5532 g_strdup(out_codeset));
5534 /* protect trailing spaces when signing message */
5535 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5536 privacy_system_can_sign(compose->privacy_system)) {
5537 encoding = ENC_QUOTED_PRINTABLE;
5540 debug_print("main text: %zd bytes encoded as %s in %d\n",
5541 strlen(buf), out_codeset, encoding);
5543 /* check for line length limit */
5544 if (action == COMPOSE_WRITE_FOR_SEND &&
5545 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5546 check_line_length(buf, 1000, &line) < 0) {
5550 msg = g_strdup_printf
5551 (_("Line %d exceeds the line length limit (998 bytes).\n"
5552 "The contents of the message might be broken on the way to the delivery.\n"
5554 "Send it anyway?"), line + 1);
5555 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5557 if (aval != G_ALERTALTERNATE) {
5563 if (encoding != ENC_UNKNOWN)
5564 procmime_encode_content(mimetext, encoding);
5566 /* append attachment parts */
5567 if (compose_use_attach(compose) && attach_parts) {
5568 MimeInfo *mimempart;
5569 gchar *boundary = NULL;
5570 mimempart = procmime_mimeinfo_new();
5571 mimempart->content = MIMECONTENT_EMPTY;
5572 mimempart->type = MIMETYPE_MULTIPART;
5573 mimempart->subtype = g_strdup("mixed");
5577 boundary = generate_mime_boundary(NULL);
5578 } while (strstr(buf, boundary) != NULL);
5580 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5583 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5585 g_node_append(mimempart->node, mimetext->node);
5586 g_node_append(mimemsg->node, mimempart->node);
5588 if (compose_add_attachments(compose, mimempart) < 0)
5591 g_node_append(mimemsg->node, mimetext->node);
5595 if (strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) != 0) {
5596 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5597 /* extract name and address */
5598 if (strstr(spec, " <") && strstr(spec, ">")) {
5599 from_addr = g_strdup(strrchr(spec, '<')+1);
5600 *(strrchr(from_addr, '>')) = '\0';
5601 from_name = g_strdup(spec);
5602 *(strrchr(from_name, '<')) = '\0';
5609 /* sign message if sending */
5610 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5611 privacy_system_can_sign(compose->privacy_system))
5612 if (!privacy_sign(compose->privacy_system, mimemsg,
5613 compose->account, from_addr)) {
5620 procmime_write_mimeinfo(mimemsg, fp);
5622 procmime_mimeinfo_free_all(mimemsg);
5627 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5629 GtkTextBuffer *buffer;
5630 GtkTextIter start, end;
5635 if ((fp = g_fopen(file, "wb")) == NULL) {
5636 FILE_OP_ERROR(file, "fopen");
5640 /* chmod for security */
5641 if (change_file_mode_rw(fp, file) < 0) {
5642 FILE_OP_ERROR(file, "chmod");
5643 g_warning("can't change file mode\n");
5646 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5647 gtk_text_buffer_get_start_iter(buffer, &start);
5648 gtk_text_buffer_get_end_iter(buffer, &end);
5649 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5651 chars = conv_codeset_strdup
5652 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5655 if (!chars) return -1;
5658 len = strlen(chars);
5659 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5660 FILE_OP_ERROR(file, "fwrite");
5669 if (fclose(fp) == EOF) {
5670 FILE_OP_ERROR(file, "fclose");
5677 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5680 MsgInfo *msginfo = compose->targetinfo;
5682 cm_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5683 if (!msginfo) return -1;
5685 if (!force && MSG_IS_LOCKED(msginfo->flags))
5688 item = msginfo->folder;
5689 cm_return_val_if_fail(item != NULL, -1);
5691 if (procmsg_msg_exist(msginfo) &&
5692 (folder_has_parent_of_type(item, F_QUEUE) ||
5693 folder_has_parent_of_type(item, F_DRAFT)
5694 || msginfo == compose->autosaved_draft)) {
5695 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5696 g_warning("can't remove the old message\n");
5699 debug_print("removed reedit target %d\n", msginfo->msgnum);
5706 static void compose_remove_draft(Compose *compose)
5709 MsgInfo *msginfo = compose->targetinfo;
5710 drafts = account_get_special_folder(compose->account, F_DRAFT);
5712 if (procmsg_msg_exist(msginfo)) {
5713 folder_item_remove_msg(drafts, msginfo->msgnum);
5718 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5719 gboolean remove_reedit_target)
5721 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5724 static gboolean compose_warn_encryption(Compose *compose)
5726 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5727 AlertValue val = G_ALERTALTERNATE;
5729 if (warning == NULL)
5732 val = alertpanel_full(_("Encryption warning"), warning,
5733 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5734 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5735 if (val & G_ALERTDISABLE) {
5736 val &= ~G_ALERTDISABLE;
5737 if (val == G_ALERTALTERNATE)
5738 privacy_inhibit_encrypt_warning(compose->privacy_system,
5742 if (val == G_ALERTALTERNATE) {
5749 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5750 gchar **msgpath, gboolean check_subject,
5751 gboolean remove_reedit_target)
5758 PrefsAccount *mailac = NULL, *newsac = NULL;
5759 gboolean err = FALSE;
5761 debug_print("queueing message...\n");
5762 cm_return_val_if_fail(compose->account != NULL, -1);
5764 if (compose_check_entries(compose, check_subject) == FALSE) {
5765 if (compose->batch) {
5766 gtk_widget_show_all(compose->window);
5771 if (!compose->to_list && !compose->newsgroup_list) {
5772 g_warning("can't get recipient list.");
5776 if (compose->to_list) {
5777 if (compose->account->protocol != A_NNTP)
5778 mailac = compose->account;
5779 else if (cur_account && cur_account->protocol != A_NNTP)
5780 mailac = cur_account;
5781 else if (!(mailac = compose_current_mail_account())) {
5782 alertpanel_error(_("No account for sending mails available!"));
5787 if (compose->newsgroup_list) {
5788 if (compose->account->protocol == A_NNTP)
5789 newsac = compose->account;
5791 alertpanel_error(_("Selected account isn't NNTP: Posting is impossible."));
5796 /* write queue header */
5797 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5798 G_DIR_SEPARATOR, compose, (guint) rand());
5799 debug_print("queuing to %s\n", tmp);
5800 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5801 FILE_OP_ERROR(tmp, "fopen");
5806 if (change_file_mode_rw(fp, tmp) < 0) {
5807 FILE_OP_ERROR(tmp, "chmod");
5808 g_warning("can't change file mode\n");
5811 /* queueing variables */
5812 err |= (fprintf(fp, "AF:\n") < 0);
5813 err |= (fprintf(fp, "NF:0\n") < 0);
5814 err |= (fprintf(fp, "PS:10\n") < 0);
5815 err |= (fprintf(fp, "SRH:1\n") < 0);
5816 err |= (fprintf(fp, "SFN:\n") < 0);
5817 err |= (fprintf(fp, "DSR:\n") < 0);
5819 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5821 err |= (fprintf(fp, "MID:\n") < 0);
5822 err |= (fprintf(fp, "CFG:\n") < 0);
5823 err |= (fprintf(fp, "PT:0\n") < 0);
5824 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5825 err |= (fprintf(fp, "RQ:\n") < 0);
5827 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5829 err |= (fprintf(fp, "SSV:\n") < 0);
5831 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5833 err |= (fprintf(fp, "NSV:\n") < 0);
5834 err |= (fprintf(fp, "SSH:\n") < 0);
5835 /* write recepient list */
5836 if (compose->to_list) {
5837 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5838 for (cur = compose->to_list->next; cur != NULL;
5840 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5841 err |= (fprintf(fp, "\n") < 0);
5843 /* write newsgroup list */
5844 if (compose->newsgroup_list) {
5845 err |= (fprintf(fp, "NG:") < 0);
5846 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5847 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5848 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5849 err |= (fprintf(fp, "\n") < 0);
5851 /* Sylpheed account IDs */
5853 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5855 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5858 if (compose->privacy_system != NULL) {
5859 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5860 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5861 if (compose->use_encryption) {
5863 if (!compose_warn_encryption(compose)) {
5869 if (mailac && mailac->encrypt_to_self) {
5870 GSList *tmp_list = g_slist_copy(compose->to_list);
5871 tmp_list = g_slist_append(tmp_list, compose->account->address);
5872 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5873 g_slist_free(tmp_list);
5875 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5877 if (encdata != NULL) {
5878 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5879 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5880 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5882 } /* else we finally dont want to encrypt */
5884 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5885 /* and if encdata was null, it means there's been a problem in
5888 g_warning("failed to write queue message");
5898 /* Save copy folder */
5899 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5900 gchar *savefolderid;
5902 savefolderid = compose_get_save_to(compose);
5903 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5904 g_free(savefolderid);
5906 /* Save copy folder */
5907 if (compose->return_receipt) {
5908 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5910 /* Message-ID of message replying to */
5911 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5914 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5915 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5918 /* Message-ID of message forwarding to */
5919 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5922 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5923 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5927 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
5928 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
5930 /* end of headers */
5931 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5933 if (compose->redirect_filename != NULL) {
5934 if (compose_redirect_write_to_file(compose, fp) < 0) {
5942 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5946 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5950 g_warning("failed to write queue message\n");
5956 if (fclose(fp) == EOF) {
5957 FILE_OP_ERROR(tmp, "fclose");
5963 if (item && *item) {
5966 queue = account_get_special_folder(compose->account, F_QUEUE);
5969 g_warning("can't find queue folder\n");
5974 folder_item_scan(queue);
5975 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5976 g_warning("can't queue the message\n");
5982 if (msgpath == NULL) {
5988 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5989 compose_remove_reedit_target(compose, FALSE);
5992 if ((msgnum != NULL) && (item != NULL)) {
6000 static int compose_add_attachments(Compose *compose, MimeInfo *parent)
6003 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
6005 struct stat statbuf;
6006 gchar *type, *subtype;
6007 GtkTreeModel *model;
6010 model = gtk_tree_view_get_model(tree_view);
6012 if (!gtk_tree_model_get_iter_first(model, &iter))
6015 gtk_tree_model_get(model, &iter,
6019 if (!is_file_exist(ainfo->file)) {
6020 gchar *msg = g_strdup_printf(_("Attachment %s doesn't exist anymore. Ignore?"), ainfo->file);
6021 AlertValue val = alertpanel_full(_("Warning"), msg, _("Cancel sending"), _("Ignore attachment"),
6022 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
6024 if (val == G_ALERTDEFAULT) {
6029 mimepart = procmime_mimeinfo_new();
6030 mimepart->content = MIMECONTENT_FILE;
6031 mimepart->data.filename = g_strdup(ainfo->file);
6032 mimepart->tmp = FALSE; /* or we destroy our attachment */
6033 mimepart->offset = 0;
6035 g_stat(ainfo->file, &statbuf);
6036 mimepart->length = statbuf.st_size;
6038 type = g_strdup(ainfo->content_type);
6040 if (!strchr(type, '/')) {
6042 type = g_strdup("application/octet-stream");
6045 subtype = strchr(type, '/') + 1;
6046 *(subtype - 1) = '\0';
6047 mimepart->type = procmime_get_media_type(type);
6048 mimepart->subtype = g_strdup(subtype);
6051 if (mimepart->type == MIMETYPE_MESSAGE &&
6052 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
6053 mimepart->disposition = DISPOSITIONTYPE_INLINE;
6054 } else if (mimepart->type == MIMETYPE_TEXT) {
6055 if (!ainfo->name && g_ascii_strcasecmp(mimepart->subtype, "plain")) {
6056 /* Text parts with no name come from multipart/alternative
6057 * forwards. Make sure the recipient won't look at the
6058 * original HTML part by mistake. */
6059 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6060 ainfo->name = g_strdup_printf(_("Original %s part"),
6064 g_hash_table_insert(mimepart->typeparameters,
6065 g_strdup("charset"), g_strdup(ainfo->charset));
6067 if (ainfo->name && mimepart->type != MIMETYPE_MESSAGE) {
6068 if (mimepart->type == MIMETYPE_APPLICATION &&
6069 !strcmp2(mimepart->subtype, "octet-stream"))
6070 g_hash_table_insert(mimepart->typeparameters,
6071 g_strdup("name"), g_strdup(ainfo->name));
6072 g_hash_table_insert(mimepart->dispositionparameters,
6073 g_strdup("filename"), g_strdup(ainfo->name));
6074 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
6077 if (mimepart->type == MIMETYPE_MESSAGE
6078 || mimepart->type == MIMETYPE_MULTIPART)
6079 ainfo->encoding = ENC_BINARY;
6080 else if (compose->use_signing) {
6081 if (ainfo->encoding == ENC_7BIT)
6082 ainfo->encoding = ENC_QUOTED_PRINTABLE;
6083 else if (ainfo->encoding == ENC_8BIT)
6084 ainfo->encoding = ENC_BASE64;
6089 procmime_encode_content(mimepart, ainfo->encoding);
6091 g_node_append(parent->node, mimepart->node);
6092 } while (gtk_tree_model_iter_next(model, &iter));
6097 #define IS_IN_CUSTOM_HEADER(header) \
6098 (compose->account->add_customhdr && \
6099 custom_header_find(compose->account->customhdr_list, header) != NULL)
6101 static void compose_add_headerfield_from_headerlist(Compose *compose,
6103 const gchar *fieldname,
6104 const gchar *seperator)
6106 gchar *str, *fieldname_w_colon;
6107 gboolean add_field = FALSE;
6109 ComposeHeaderEntry *headerentry;
6110 const gchar *headerentryname;
6111 const gchar *trans_fieldname;
6114 if (IS_IN_CUSTOM_HEADER(fieldname))
6117 debug_print("Adding %s-fields\n", fieldname);
6119 fieldstr = g_string_sized_new(64);
6121 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
6122 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
6124 for (list = compose->header_list; list; list = list->next) {
6125 headerentry = ((ComposeHeaderEntry *)list->data);
6126 headerentryname = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo)))));
6128 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
6129 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6131 if (str[0] != '\0') {
6133 g_string_append(fieldstr, seperator);
6134 g_string_append(fieldstr, str);
6143 buf = g_new0(gchar, fieldstr->len * 4 + 256);
6144 compose_convert_header
6145 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
6146 strlen(fieldname) + 2, TRUE);
6147 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
6151 g_free(fieldname_w_colon);
6152 g_string_free(fieldstr, TRUE);
6157 static gchar *compose_get_manual_headers_info(Compose *compose)
6159 GString *sh_header = g_string_new(" ");
6161 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6163 for (list = compose->header_list; list; list = list->next) {
6164 ComposeHeaderEntry *headerentry;
6167 gchar *headername_wcolon;
6168 const gchar *headername_trans;
6170 gboolean standard_header = FALSE;
6172 headerentry = ((ComposeHeaderEntry *)list->data);
6174 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6176 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6181 if (!strstr(tmp, ":")) {
6182 headername_wcolon = g_strconcat(tmp, ":", NULL);
6183 headername = g_strdup(tmp);
6185 headername_wcolon = g_strdup(tmp);
6186 headername = g_strdup(strtok(tmp, ":"));
6190 string = std_headers;
6191 while (*string != NULL) {
6192 headername_trans = prefs_common_translated_header_name(*string);
6193 if (!strcmp(headername_trans, headername_wcolon))
6194 standard_header = TRUE;
6197 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6198 g_string_append_printf(sh_header, "%s ", headername);
6200 g_free(headername_wcolon);
6202 g_string_truncate(sh_header, strlen(sh_header->str) - 1); /* remove last space */
6203 return g_string_free(sh_header, FALSE);
6206 static gchar *compose_get_header(Compose *compose)
6208 gchar buf[BUFFSIZE];
6209 const gchar *entry_str;
6213 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6215 gchar *from_name = NULL, *from_address = NULL;
6218 cm_return_val_if_fail(compose->account != NULL, NULL);
6219 cm_return_val_if_fail(compose->account->address != NULL, NULL);
6221 header = g_string_sized_new(64);
6224 get_rfc822_date(buf, sizeof(buf));
6225 g_string_append_printf(header, "Date: %s\n", buf);
6229 if (compose->account->name && *compose->account->name) {
6231 QUOTE_IF_REQUIRED(buf, compose->account->name);
6232 tmp = g_strdup_printf("%s <%s>",
6233 buf, compose->account->address);
6235 tmp = g_strdup_printf("%s",
6236 compose->account->address);
6238 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
6239 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
6241 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
6242 from_address = g_strdup(compose->account->address);
6244 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
6245 /* extract name and address */
6246 if (strstr(spec, " <") && strstr(spec, ">")) {
6247 from_address = g_strdup(strrchr(spec, '<')+1);
6248 *(strrchr(from_address, '>')) = '\0';
6249 from_name = g_strdup(spec);
6250 *(strrchr(from_name, '<')) = '\0';
6253 from_address = g_strdup(spec);
6260 if (from_name && *from_name) {
6261 compose_convert_header
6262 (compose, buf, sizeof(buf), from_name,
6263 strlen("From: "), TRUE);
6264 QUOTE_IF_REQUIRED(name, buf);
6266 g_string_append_printf(header, "From: %s <%s>\n",
6267 name, from_address);
6269 g_string_append_printf(header, "From: %s\n", from_address);
6272 g_free(from_address);
6275 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
6278 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
6281 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
6285 * If this account is a NNTP account remove Bcc header from
6286 * message body since it otherwise will be publicly shown
6288 if (compose->account->protocol != A_NNTP)
6289 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
6292 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
6294 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
6297 compose_convert_header(compose, buf, sizeof(buf), str,
6298 strlen("Subject: "), FALSE);
6299 g_string_append_printf(header, "Subject: %s\n", buf);
6305 if (compose->account->set_domain && compose->account->domain) {
6306 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
6307 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
6308 g_snprintf(buf, sizeof(buf), "%s",
6309 strchr(compose->account->address, '@') ?
6310 strchr(compose->account->address, '@')+1 :
6311 compose->account->address);
6313 g_snprintf(buf, sizeof(buf), "%s", "");
6316 if (compose->account->gen_msgid) {
6318 if (compose->account->msgid_with_addr) {
6319 addr = compose->account->address;
6321 generate_msgid(buf, sizeof(buf), addr);
6322 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
6323 compose->msgid = g_strdup(buf);
6325 compose->msgid = NULL;
6328 if (compose->remove_references == FALSE) {
6330 if (compose->inreplyto && compose->to_list)
6331 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
6334 if (compose->references)
6335 g_string_append_printf(header, "References: %s\n", compose->references);
6339 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
6342 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6345 if (compose->account->organization &&
6346 strlen(compose->account->organization) &&
6347 !IS_IN_CUSTOM_HEADER("Organization")) {
6348 compose_convert_header(compose, buf, sizeof(buf),
6349 compose->account->organization,
6350 strlen("Organization: "), FALSE);
6351 g_string_append_printf(header, "Organization: %s\n", buf);
6354 /* Program version and system info */
6355 if (compose->account->gen_xmailer &&
6356 g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6357 !compose->newsgroup_list) {
6358 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6360 gtk_major_version, gtk_minor_version, gtk_micro_version,
6363 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6364 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6366 gtk_major_version, gtk_minor_version, gtk_micro_version,
6370 /* custom headers */
6371 if (compose->account->add_customhdr) {
6374 for (cur = compose->account->customhdr_list; cur != NULL;
6376 CustomHeader *chdr = (CustomHeader *)cur->data;
6378 if (custom_header_is_allowed(chdr->name)
6379 && chdr->value != NULL
6380 && *(chdr->value) != '\0') {
6381 compose_convert_header
6382 (compose, buf, sizeof(buf),
6384 strlen(chdr->name) + 2, FALSE);
6385 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6390 /* Automatic Faces and X-Faces */
6391 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6392 g_string_append_printf(header, "X-Face: %s\n", buf);
6394 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6395 g_string_append_printf(header, "X-Face: %s\n", buf);
6397 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6398 g_string_append_printf(header, "Face: %s\n", buf);
6400 else if (get_default_face (buf, sizeof(buf)) == 0) {
6401 g_string_append_printf(header, "Face: %s\n", buf);
6405 switch (compose->priority) {
6406 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6407 "X-Priority: 1 (Highest)\n");
6409 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6410 "X-Priority: 2 (High)\n");
6412 case PRIORITY_NORMAL: break;
6413 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6414 "X-Priority: 4 (Low)\n");
6416 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6417 "X-Priority: 5 (Lowest)\n");
6419 default: debug_print("compose: priority unknown : %d\n",
6423 /* Request Return Receipt */
6424 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6425 if (compose->return_receipt) {
6426 if (compose->account->name
6427 && *compose->account->name) {
6428 compose_convert_header(compose, buf, sizeof(buf),
6429 compose->account->name,
6430 strlen("Disposition-Notification-To: "),
6432 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6434 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6438 /* get special headers */
6439 for (list = compose->header_list; list; list = list->next) {
6440 ComposeHeaderEntry *headerentry;
6443 gchar *headername_wcolon;
6444 const gchar *headername_trans;
6447 gboolean standard_header = FALSE;
6449 headerentry = ((ComposeHeaderEntry *)list->data);
6451 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((headerentry->combo))))));
6453 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6458 if (!strstr(tmp, ":")) {
6459 headername_wcolon = g_strconcat(tmp, ":", NULL);
6460 headername = g_strdup(tmp);
6462 headername_wcolon = g_strdup(tmp);
6463 headername = g_strdup(strtok(tmp, ":"));
6467 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6468 Xstrdup_a(headervalue, entry_str, return NULL);
6469 subst_char(headervalue, '\r', ' ');
6470 subst_char(headervalue, '\n', ' ');
6471 string = std_headers;
6472 while (*string != NULL) {
6473 headername_trans = prefs_common_translated_header_name(*string);
6474 if (!strcmp(headername_trans, headername_wcolon))
6475 standard_header = TRUE;
6478 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6479 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6482 g_free(headername_wcolon);
6486 g_string_free(header, FALSE);
6491 #undef IS_IN_CUSTOM_HEADER
6493 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6494 gint header_len, gboolean addr_field)
6496 gchar *tmpstr = NULL;
6497 const gchar *out_codeset = NULL;
6499 cm_return_if_fail(src != NULL);
6500 cm_return_if_fail(dest != NULL);
6502 if (len < 1) return;
6504 tmpstr = g_strdup(src);
6506 subst_char(tmpstr, '\n', ' ');
6507 subst_char(tmpstr, '\r', ' ');
6510 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6511 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6512 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6517 codeconv_set_strict(TRUE);
6518 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6519 conv_get_charset_str(compose->out_encoding));
6520 codeconv_set_strict(FALSE);
6522 if (!dest || *dest == '\0') {
6523 gchar *test_conv_global_out = NULL;
6524 gchar *test_conv_reply = NULL;
6526 /* automatic mode. be automatic. */
6527 codeconv_set_strict(TRUE);
6529 out_codeset = conv_get_outgoing_charset_str();
6531 debug_print("trying to convert to %s\n", out_codeset);
6532 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6535 if (!test_conv_global_out && compose->orig_charset
6536 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6537 out_codeset = compose->orig_charset;
6538 debug_print("failure; trying to convert to %s\n", out_codeset);
6539 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6542 if (!test_conv_global_out && !test_conv_reply) {
6544 out_codeset = CS_INTERNAL;
6545 debug_print("finally using %s\n", out_codeset);
6547 g_free(test_conv_global_out);
6548 g_free(test_conv_reply);
6549 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6551 codeconv_set_strict(FALSE);
6556 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6560 cm_return_if_fail(user_data != NULL);
6562 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6563 g_strstrip(address);
6564 if (*address != '\0') {
6565 gchar *name = procheader_get_fromname(address);
6566 extract_address(address);
6567 #ifndef USE_NEW_ADDRBOOK
6568 addressbook_add_contact(name, address, NULL, NULL);
6570 debug_print("%s: %s\n", name, address);
6571 if (addressadd_selection(name, address, NULL, NULL)) {
6572 debug_print( "addressbook_add_contact - added\n" );
6579 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6581 GtkWidget *menuitem;
6584 cm_return_if_fail(menu != NULL);
6585 cm_return_if_fail(GTK_IS_MENU_SHELL(menu));
6587 menuitem = gtk_separator_menu_item_new();
6588 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6589 gtk_widget_show(menuitem);
6591 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6592 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6594 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6595 g_strstrip(address);
6596 if (*address == '\0') {
6597 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6600 g_signal_connect(G_OBJECT(menuitem), "activate",
6601 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6602 gtk_widget_show(menuitem);
6605 void compose_add_extra_header(gchar *header, GtkListStore *model)
6608 if (strcmp(header, "")) {
6609 COMBOBOX_ADD(model, header, COMPOSE_TO);
6613 void compose_add_extra_header_entries(GtkListStore *model)
6617 gchar buf[BUFFSIZE];
6620 if (extra_headers == NULL) {
6621 exhrc = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, "extraheaderrc", NULL);
6622 if ((exh = g_fopen(exhrc, "rb")) == NULL) {
6623 debug_print("extra headers file not found\n");
6624 goto extra_headers_done;
6626 while (fgets(buf, BUFFSIZE, exh) != NULL) {
6627 lastc = strlen(buf) - 1; /* remove trailing \n */
6628 buf[lastc] = (buf[lastc] == '\n')? '\0': buf[lastc];
6630 if (lastc > 0 && buf[0] != '#' && buf[lastc] == ':') {
6631 buf[lastc] = '\0'; /* remove trailing : for comparison */
6632 if (custom_header_is_allowed(buf)) {
6634 extra_headers = g_slist_prepend(extra_headers, g_strdup(buf));
6637 g_message("disallowed extra header line: %s\n", buf);
6641 g_message("invalid extra header line: %s\n", buf);
6647 extra_headers = g_slist_prepend(extra_headers, g_strdup("")); /* end of list */
6648 extra_headers = g_slist_reverse(extra_headers);
6650 g_slist_foreach(extra_headers, (GFunc)compose_add_extra_header, (gpointer)model);
6653 static void compose_create_header_entry(Compose *compose)
6655 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6662 const gchar *header = NULL;
6663 ComposeHeaderEntry *headerentry;
6664 gboolean standard_header = FALSE;
6665 GtkListStore *model;
6667 #if !(GTK_CHECK_VERSION(2,12,0))
6668 GtkTooltips *tips = compose->tooltips;
6671 headerentry = g_new0(ComposeHeaderEntry, 1);
6673 /* Combo box model */
6674 model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
6675 #if !GTK_CHECK_VERSION(2, 24, 0)
6676 combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(model), 0);
6678 COMBOBOX_ADD(model, prefs_common_translated_header_name("To:"),
6680 COMBOBOX_ADD(model, prefs_common_translated_header_name("Cc:"),
6682 COMBOBOX_ADD(model, prefs_common_translated_header_name("Bcc:"),
6684 COMBOBOX_ADD(model, prefs_common_translated_header_name("Newsgroups:"),
6685 COMPOSE_NEWSGROUPS);
6686 COMBOBOX_ADD(model, prefs_common_translated_header_name("Reply-To:"),
6688 COMBOBOX_ADD(model, prefs_common_translated_header_name("Followup-To:"),
6689 COMPOSE_FOLLOWUPTO);
6690 compose_add_extra_header_entries(model);
6693 #if GTK_CHECK_VERSION(2, 24, 0)
6694 combo = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(model));
6695 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
6696 gtk_cell_renderer_set_alignment(cell, 0.0, 0.5);
6697 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, TRUE);
6698 gtk_combo_box_set_entry_text_column(combo, 0);
6700 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6701 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN(combo))), "grab_focus",
6702 G_CALLBACK(compose_grab_focus_cb), compose);
6703 gtk_widget_show(combo);
6706 l = g_list_prepend(l, gtk_bin_get_child(GTK_BIN(combo)));
6707 gtk_container_set_focus_chain(GTK_CONTAINER(combo), l);
6710 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6711 compose->header_nextrow, compose->header_nextrow+1,
6712 GTK_SHRINK, GTK_FILL, 0, 0);
6713 if (compose->header_last && (compose->draft_timeout_tag != -2)) {
6714 const gchar *last_header_entry = gtk_entry_get_text(
6715 GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6717 while (*string != NULL) {
6718 if (!strcmp(prefs_common_translated_header_name(*string), last_header_entry))
6719 standard_header = TRUE;
6722 if (standard_header)
6723 header = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
6725 if (!compose->header_last || !standard_header) {
6726 switch(compose->account->protocol) {
6728 header = prefs_common_translated_header_name("Newsgroups:");
6731 header = prefs_common_translated_header_name("To:");
6736 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
6738 g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
6739 G_CALLBACK(compose_grab_focus_cb), compose);
6741 /* Entry field with cleanup button */
6742 button = gtk_button_new();
6743 gtk_button_set_image(GTK_BUTTON(button),
6744 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU));
6745 gtk_widget_show(button);
6746 CLAWS_SET_TIP(button,
6747 _("Delete entry contents"));
6748 entry = gtk_entry_new();
6749 gtk_widget_show(entry);
6750 CLAWS_SET_TIP(entry,
6751 _("Use <tab> to autocomplete from addressbook"));
6752 hbox = gtk_hbox_new (FALSE, 0);
6753 gtk_widget_show(hbox);
6754 gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
6755 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
6756 gtk_table_attach(GTK_TABLE(compose->header_table), hbox, 1, 2,
6757 compose->header_nextrow, compose->header_nextrow+1,
6758 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6760 g_signal_connect(G_OBJECT(entry), "key-press-event",
6761 G_CALLBACK(compose_headerentry_key_press_event_cb),
6763 g_signal_connect(G_OBJECT(entry), "changed",
6764 G_CALLBACK(compose_headerentry_changed_cb),
6766 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6767 G_CALLBACK(compose_grab_focus_cb), compose);
6769 g_signal_connect(G_OBJECT(button), "clicked",
6770 G_CALLBACK(compose_headerentry_button_clicked_cb),
6774 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6775 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6776 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6777 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6778 G_CALLBACK(compose_header_drag_received_cb),
6780 g_signal_connect(G_OBJECT(entry), "drag-drop",
6781 G_CALLBACK(compose_drag_drop),
6783 g_signal_connect(G_OBJECT(entry), "populate-popup",
6784 G_CALLBACK(compose_entry_popup_extend),
6787 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6789 headerentry->compose = compose;
6790 headerentry->combo = combo;
6791 headerentry->entry = entry;
6792 headerentry->button = button;
6793 headerentry->hbox = hbox;
6794 headerentry->headernum = compose->header_nextrow;
6795 headerentry->type = PREF_NONE;
6797 compose->header_nextrow++;
6798 compose->header_last = headerentry;
6799 compose->header_list =
6800 g_slist_append(compose->header_list,
6804 static void compose_add_header_entry(Compose *compose, const gchar *header,
6805 gchar *text, ComposePrefType pref_type)
6807 ComposeHeaderEntry *last_header = compose->header_last;
6808 gchar *tmp = g_strdup(text), *email;
6809 gboolean replyto_hdr;
6811 replyto_hdr = (!strcasecmp(header,
6812 prefs_common_translated_header_name("Reply-To:")) ||
6814 prefs_common_translated_header_name("Followup-To:")) ||
6816 prefs_common_translated_header_name("In-Reply-To:")));
6818 extract_address(tmp);
6819 email = g_utf8_strdown(tmp, -1);
6821 if (replyto_hdr == FALSE &&
6822 g_hash_table_lookup(compose->email_hashtable, email) != NULL)
6824 debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
6825 header, text, (gint) pref_type);
6831 if (!strcasecmp(header, prefs_common_translated_header_name("In-Reply-To:")))
6832 gtk_entry_set_text(GTK_ENTRY(
6833 gtk_bin_get_child(GTK_BIN(last_header->combo))), header);
6835 combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
6836 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6837 last_header->type = pref_type;
6839 if (replyto_hdr == FALSE)
6840 g_hash_table_insert(compose->email_hashtable, email,
6841 GUINT_TO_POINTER(1));
6848 static void compose_destroy_headerentry(Compose *compose,
6849 ComposeHeaderEntry *headerentry)
6851 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
6854 extract_address(text);
6855 email = g_utf8_strdown(text, -1);
6856 g_hash_table_remove(compose->email_hashtable, email);
6860 gtk_widget_destroy(headerentry->combo);
6861 gtk_widget_destroy(headerentry->entry);
6862 gtk_widget_destroy(headerentry->button);
6863 gtk_widget_destroy(headerentry->hbox);
6864 g_free(headerentry);
6867 static void compose_remove_header_entries(Compose *compose)
6870 for (list = compose->header_list; list; list = list->next)
6871 compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
6873 compose->header_last = NULL;
6874 g_slist_free(compose->header_list);
6875 compose->header_list = NULL;
6876 compose->header_nextrow = 1;
6877 compose_create_header_entry(compose);
6880 static GtkWidget *compose_create_header(Compose *compose)
6882 GtkWidget *from_optmenu_hbox;
6883 GtkWidget *header_scrolledwin_main;
6884 GtkWidget *header_table_main;
6885 GtkWidget *header_scrolledwin;
6886 GtkWidget *header_table;
6888 /* parent with account selection and from header */
6889 header_scrolledwin_main = gtk_scrolled_window_new(NULL, NULL);
6890 gtk_widget_show(header_scrolledwin_main);
6891 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin_main), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6893 header_table_main = gtk_table_new(2, 2, FALSE);
6894 gtk_widget_show(header_table_main);
6895 gtk_container_set_border_width(GTK_CONTAINER(header_table_main), BORDER_WIDTH);
6896 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin_main), header_table_main);
6897 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin_main)))), GTK_SHADOW_NONE);
6899 from_optmenu_hbox = compose_account_option_menu_create(compose);
6900 gtk_table_attach(GTK_TABLE(header_table_main), from_optmenu_hbox,
6901 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6903 /* child with header labels and entries */
6904 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6905 gtk_widget_show(header_scrolledwin);
6906 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6908 header_table = gtk_table_new(2, 2, FALSE);
6909 gtk_widget_show(header_table);
6910 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6911 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6912 gtk_viewport_set_shadow_type(GTK_VIEWPORT(gtk_bin_get_child(GTK_BIN((header_scrolledwin)))), GTK_SHADOW_NONE);
6914 gtk_table_attach(GTK_TABLE(header_table_main), header_scrolledwin,
6915 0, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 2);
6917 compose->header_table = header_table;
6918 compose->header_list = NULL;
6919 compose->header_nextrow = 0;
6921 compose_create_header_entry(compose);
6923 compose->table = NULL;
6925 return header_scrolledwin_main;
6928 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6930 Compose *compose = (Compose *)data;
6931 GdkEventButton event;
6934 event.time = gtk_get_current_event_time();
6936 return attach_button_pressed(compose->attach_clist, &event, compose);
6939 static GtkWidget *compose_create_attach(Compose *compose)
6941 GtkWidget *attach_scrwin;
6942 GtkWidget *attach_clist;
6944 GtkListStore *store;
6945 GtkCellRenderer *renderer;
6946 GtkTreeViewColumn *column;
6947 GtkTreeSelection *selection;
6949 /* attachment list */
6950 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6951 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6952 GTK_POLICY_AUTOMATIC,
6953 GTK_POLICY_AUTOMATIC);
6954 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6956 store = gtk_list_store_new(N_ATTACH_COLS,
6962 G_TYPE_AUTO_POINTER,
6964 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6965 (GTK_TREE_MODEL(store)));
6966 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6967 g_object_unref(store);
6969 renderer = gtk_cell_renderer_text_new();
6970 column = gtk_tree_view_column_new_with_attributes
6971 (_("Mime type"), renderer, "text",
6972 COL_MIMETYPE, NULL);
6973 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6975 renderer = gtk_cell_renderer_text_new();
6976 column = gtk_tree_view_column_new_with_attributes
6977 (_("Size"), renderer, "text",
6979 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6981 renderer = gtk_cell_renderer_text_new();
6982 column = gtk_tree_view_column_new_with_attributes
6983 (_("Name"), renderer, "text",
6985 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6987 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6988 prefs_common.use_stripes_everywhere);
6989 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6990 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6992 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6993 G_CALLBACK(attach_selected), compose);
6994 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6995 G_CALLBACK(attach_button_pressed), compose);
6997 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6998 G_CALLBACK(popup_attach_button_pressed), compose);
7000 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
7001 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7002 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
7003 G_CALLBACK(popup_attach_button_pressed), compose);
7005 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
7006 G_CALLBACK(attach_key_pressed), compose);
7009 gtk_drag_dest_set(attach_clist,
7010 GTK_DEST_DEFAULT_ALL, compose_mime_types,
7011 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7012 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7013 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
7014 G_CALLBACK(compose_attach_drag_received_cb),
7016 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
7017 G_CALLBACK(compose_drag_drop),
7020 compose->attach_scrwin = attach_scrwin;
7021 compose->attach_clist = attach_clist;
7023 return attach_scrwin;
7026 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
7027 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
7029 static GtkWidget *compose_create_others(Compose *compose)
7032 GtkWidget *savemsg_checkbtn;
7033 GtkWidget *savemsg_combo;
7034 GtkWidget *savemsg_select;
7037 gchar *folderidentifier;
7039 /* Table for settings */
7040 table = gtk_table_new(3, 1, FALSE);
7041 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
7042 gtk_widget_show(table);
7043 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
7046 /* Save Message to folder */
7047 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
7048 gtk_widget_show(savemsg_checkbtn);
7049 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7050 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7051 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
7053 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
7054 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
7056 #if !GTK_CHECK_VERSION(2, 24, 0)
7057 savemsg_combo = gtk_combo_box_entry_new_text();
7059 savemsg_combo = gtk_combo_box_text_new_with_entry();
7061 compose->savemsg_checkbtn = savemsg_checkbtn;
7062 compose->savemsg_combo = savemsg_combo;
7063 gtk_widget_show(savemsg_combo);
7065 if (prefs_common.compose_save_to_history)
7066 combobox_set_popdown_strings(GTK_COMBO_BOX(savemsg_combo),
7067 prefs_common.compose_save_to_history);
7069 gtk_table_attach(GTK_TABLE(table), savemsg_combo, 1, 2, rowcount, rowcount + 1, GTK_FILL|GTK_EXPAND, GTK_SHRINK, 0, 0);
7070 gtk_widget_set_sensitive(GTK_WIDGET(savemsg_combo), prefs_common.savemsg);
7071 g_signal_connect_after(G_OBJECT(savemsg_combo), "grab_focus",
7072 G_CALLBACK(compose_grab_focus_cb), compose);
7073 if (account_get_special_folder(compose->account, F_OUTBOX)) {
7074 folderidentifier = folder_item_get_identifier(account_get_special_folder
7075 (compose->account, F_OUTBOX));
7076 compose_set_save_to(compose, folderidentifier);
7077 g_free(folderidentifier);
7080 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
7081 gtk_widget_show(savemsg_select);
7082 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
7083 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
7084 G_CALLBACK(compose_savemsg_select_cb),
7090 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
7092 gtk_widget_set_sensitive(GTK_WIDGET(compose->savemsg_combo),
7093 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
7096 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
7101 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
7104 path = folder_item_get_identifier(dest);
7106 compose_set_save_to(compose, path);
7110 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
7111 GdkAtom clip, GtkTextIter *insert_place);
7114 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
7118 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7120 if (event->button == 3) {
7122 GtkTextIter sel_start, sel_end;
7123 gboolean stuff_selected;
7125 /* move the cursor to allow GtkAspell to check the word
7126 * under the mouse */
7127 if (event->x && event->y) {
7128 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7129 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7131 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7134 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
7135 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7138 stuff_selected = gtk_text_buffer_get_selection_bounds(
7140 &sel_start, &sel_end);
7142 gtk_text_buffer_place_cursor (buffer, &iter);
7143 /* reselect stuff */
7145 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
7146 gtk_text_buffer_select_range(buffer,
7147 &sel_start, &sel_end);
7149 return FALSE; /* pass the event so that the right-click goes through */
7152 if (event->button == 2) {
7157 /* get the middle-click position to paste at the correct place */
7158 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
7159 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
7161 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
7164 entry_paste_clipboard(compose, text,
7165 prefs_common.linewrap_pastes,
7166 GDK_SELECTION_PRIMARY, &iter);
7174 static void compose_spell_menu_changed(void *data)
7176 Compose *compose = (Compose *)data;
7178 GtkWidget *menuitem;
7179 GtkWidget *parent_item;
7180 GtkMenu *menu = GTK_MENU(gtk_menu_new());
7183 if (compose->gtkaspell == NULL)
7186 parent_item = gtk_ui_manager_get_widget(compose->ui_manager,
7187 "/Menu/Spelling/Options");
7189 /* setting the submenu removes /Spelling/Options from the factory
7190 * so we need to save it */
7192 if (parent_item == NULL) {
7193 parent_item = compose->aspell_options_menu;
7194 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), NULL);
7196 compose->aspell_options_menu = parent_item;
7198 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
7200 spell_menu = g_slist_reverse(spell_menu);
7201 for (items = spell_menu;
7202 items; items = items->next) {
7203 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
7204 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
7205 gtk_widget_show(GTK_WIDGET(menuitem));
7207 g_slist_free(spell_menu);
7209 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
7210 gtk_widget_show(parent_item);
7213 static void compose_dict_changed(void *data)
7215 Compose *compose = (Compose *) data;
7217 if(compose->gtkaspell &&
7218 compose->gtkaspell->recheck_when_changing_dict == FALSE)
7221 gtkaspell_highlight_all(compose->gtkaspell);
7222 claws_spell_entry_recheck_all(CLAWS_SPELL_ENTRY(compose->subject_entry));
7226 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
7228 Compose *compose = (Compose *)data;
7229 GdkEventButton event;
7232 event.time = gtk_get_current_event_time();
7236 return text_clicked(compose->text, &event, compose);
7239 static gboolean compose_force_window_origin = TRUE;
7240 static Compose *compose_create(PrefsAccount *account,
7249 GtkWidget *handlebox;
7251 GtkWidget *notebook;
7253 GtkWidget *attach_hbox;
7254 GtkWidget *attach_lab1;
7255 GtkWidget *attach_lab2;
7260 GtkWidget *subject_hbox;
7261 GtkWidget *subject_frame;
7262 GtkWidget *subject_entry;
7266 GtkWidget *edit_vbox;
7267 GtkWidget *ruler_hbox;
7269 GtkWidget *scrolledwin;
7271 GtkTextBuffer *buffer;
7272 GtkClipboard *clipboard;
7274 UndoMain *undostruct;
7276 GtkWidget *popupmenu;
7277 GtkWidget *tmpl_menu;
7278 GtkActionGroup *action_group = NULL;
7281 GtkAspell * gtkaspell = NULL;
7284 static GdkGeometry geometry;
7286 cm_return_val_if_fail(account != NULL, NULL);
7288 debug_print("Creating compose window...\n");
7289 compose = g_new0(Compose, 1);
7291 compose->batch = batch;
7292 compose->account = account;
7293 compose->folder = folder;
7295 compose->mutex = cm_mutex_new();
7296 compose->set_cursor_pos = -1;
7298 #if !(GTK_CHECK_VERSION(2,12,0))
7299 compose->tooltips = tips;
7302 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
7304 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
7305 gtk_widget_set_size_request(window, prefs_common.compose_width,
7306 prefs_common.compose_height);
7308 if (!geometry.max_width) {
7309 geometry.max_width = gdk_screen_width();
7310 geometry.max_height = gdk_screen_height();
7313 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7314 &geometry, GDK_HINT_MAX_SIZE);
7315 if (!geometry.min_width) {
7316 geometry.min_width = 600;
7317 geometry.min_height = 440;
7319 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
7320 &geometry, GDK_HINT_MIN_SIZE);
7322 #ifndef GENERIC_UMPC
7323 if (compose_force_window_origin)
7324 gtk_window_move(GTK_WINDOW(window), prefs_common.compose_x,
7325 prefs_common.compose_y);
7327 g_signal_connect(G_OBJECT(window), "delete_event",
7328 G_CALLBACK(compose_delete_cb), compose);
7329 MANAGE_WINDOW_SIGNALS_CONNECT(window);
7330 gtk_widget_realize(window);
7332 gtkut_widget_set_composer_icon(window);
7334 vbox = gtk_vbox_new(FALSE, 0);
7335 gtk_container_add(GTK_CONTAINER(window), vbox);
7337 compose->ui_manager = gtk_ui_manager_new();
7338 action_group = cm_menu_create_action_group_full(compose->ui_manager,"Menu", compose_entries,
7339 G_N_ELEMENTS(compose_entries), (gpointer)compose);
7340 gtk_action_group_add_toggle_actions(action_group, compose_toggle_entries,
7341 G_N_ELEMENTS(compose_toggle_entries), (gpointer)compose);
7342 gtk_action_group_add_radio_actions(action_group, compose_radio_rm_entries,
7343 G_N_ELEMENTS(compose_radio_rm_entries), COMPOSE_REPLY, G_CALLBACK(compose_reply_change_mode_cb), (gpointer)compose);
7344 gtk_action_group_add_radio_actions(action_group, compose_radio_prio_entries,
7345 G_N_ELEMENTS(compose_radio_prio_entries), PRIORITY_NORMAL, G_CALLBACK(compose_set_priority_cb), (gpointer)compose);
7346 gtk_action_group_add_radio_actions(action_group, compose_radio_enc_entries,
7347 G_N_ELEMENTS(compose_radio_enc_entries), C_AUTO, G_CALLBACK(compose_set_encoding_cb), (gpointer)compose);
7350 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_MENUBAR)
7352 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Menu", NULL, GTK_UI_MANAGER_POPUP)
7355 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Message", "Message", GTK_UI_MANAGER_MENU)
7356 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Edit", "Edit", GTK_UI_MANAGER_MENU)
7358 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Spelling", "Spelling", GTK_UI_MANAGER_MENU)
7360 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Options", "Options", GTK_UI_MANAGER_MENU)
7361 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Tools", "Tools", GTK_UI_MANAGER_MENU)
7362 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu", "Help", "Help", GTK_UI_MANAGER_MENU)
7365 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Send", "Message/Send", GTK_UI_MANAGER_MENUITEM)
7366 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "SendLater", "Message/SendLater", GTK_UI_MANAGER_MENUITEM)
7367 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator1", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7368 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "AttachFile", "Message/AttachFile", GTK_UI_MANAGER_MENUITEM)
7369 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertFile", "Message/InsertFile", GTK_UI_MANAGER_MENUITEM)
7370 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "InsertSig", "Message/InsertSig", GTK_UI_MANAGER_MENUITEM)
7371 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator2", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7372 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Save", "Message/Save", GTK_UI_MANAGER_MENUITEM)
7373 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator3", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7374 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Print", "Message/Print", GTK_UI_MANAGER_MENUITEM)
7375 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Separator4", "Message/---", GTK_UI_MANAGER_SEPARATOR)
7376 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Message", "Close", "Message/Close", GTK_UI_MANAGER_MENUITEM)
7379 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Undo", "Edit/Undo", GTK_UI_MANAGER_MENUITEM)
7380 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Redo", "Edit/Redo", GTK_UI_MANAGER_MENUITEM)
7381 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator1", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7383 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Cut", "Edit/Cut", GTK_UI_MANAGER_MENUITEM)
7384 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Copy", "Edit/Copy", GTK_UI_MANAGER_MENUITEM)
7385 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Paste", "Edit/Paste", GTK_UI_MANAGER_MENUITEM)
7387 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SpecialPaste", "Edit/SpecialPaste", GTK_UI_MANAGER_MENU)
7388 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "AsQuotation", "Edit/SpecialPaste/AsQuotation", GTK_UI_MANAGER_MENUITEM)
7389 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Wrapped", "Edit/SpecialPaste/Wrapped", GTK_UI_MANAGER_MENUITEM)
7390 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/SpecialPaste", "Unwrapped", "Edit/SpecialPaste/Unwrapped", GTK_UI_MANAGER_MENUITEM)
7392 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "SelectAll", "Edit/SelectAll", GTK_UI_MANAGER_MENUITEM)
7394 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Advanced", "Edit/Advanced", GTK_UI_MANAGER_MENU)
7395 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackChar", "Edit/Advanced/BackChar", GTK_UI_MANAGER_MENUITEM)
7396 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwChar", "Edit/Advanced/ForwChar", GTK_UI_MANAGER_MENUITEM)
7397 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BackWord", "Edit/Advanced/BackWord", GTK_UI_MANAGER_MENUITEM)
7398 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "ForwWord", "Edit/Advanced/ForwWord", GTK_UI_MANAGER_MENUITEM)
7399 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "BegLine", "Edit/Advanced/BegLine", GTK_UI_MANAGER_MENUITEM)
7400 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "EndLine", "Edit/Advanced/EndLine", GTK_UI_MANAGER_MENUITEM)
7401 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "PrevLine", "Edit/Advanced/PrevLine", GTK_UI_MANAGER_MENUITEM)
7402 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "NextLine", "Edit/Advanced/NextLine", GTK_UI_MANAGER_MENUITEM)
7403 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackChar", "Edit/Advanced/DelBackChar", GTK_UI_MANAGER_MENUITEM)
7404 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwChar", "Edit/Advanced/DelForwChar", GTK_UI_MANAGER_MENUITEM)
7405 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelBackWord", "Edit/Advanced/DelBackWord", GTK_UI_MANAGER_MENUITEM)
7406 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelForwWord", "Edit/Advanced/DelForwWord", GTK_UI_MANAGER_MENUITEM)
7407 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelLine", "Edit/Advanced/DelLine", GTK_UI_MANAGER_MENUITEM)
7408 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit/Advanced", "DelEndLine", "Edit/Advanced/DelEndLine", GTK_UI_MANAGER_MENUITEM)
7410 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator2", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7412 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Find", "Edit/Find", GTK_UI_MANAGER_MENUITEM)
7413 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapPara", "Edit/WrapPara", GTK_UI_MANAGER_MENUITEM)
7414 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "WrapAllLines", "Edit/WrapAllLines", GTK_UI_MANAGER_MENUITEM)
7415 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoWrap", "Edit/AutoWrap", GTK_UI_MANAGER_MENUITEM)
7416 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "AutoIndent", "Edit/AutoIndent", GTK_UI_MANAGER_MENUITEM)
7418 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "Separator3", "Edit/---", GTK_UI_MANAGER_SEPARATOR)
7420 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Edit", "ExtEditor", "Edit/ExtEditor", GTK_UI_MANAGER_MENUITEM)
7424 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckAllSel", "Spelling/CheckAllSel", GTK_UI_MANAGER_MENUITEM)
7425 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "HighlightAll", "Spelling/HighlightAll", GTK_UI_MANAGER_MENUITEM)
7426 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "CheckBackwards", "Spelling/CheckBackwards", GTK_UI_MANAGER_MENUITEM)
7427 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "ForwardNext", "Spelling/ForwardNext", GTK_UI_MANAGER_MENUITEM)
7428 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Separator1", "Spelling/---", GTK_UI_MANAGER_SEPARATOR)
7429 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Spelling", "Options", "Spelling/Options", GTK_UI_MANAGER_MENU)
7433 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "ReplyMode", "Options/ReplyMode", GTK_UI_MANAGER_MENU)
7434 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Normal", "Options/ReplyMode/Normal", GTK_UI_MANAGER_MENUITEM)
7435 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "All", "Options/ReplyMode/All", GTK_UI_MANAGER_MENUITEM)
7436 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "Sender", "Options/ReplyMode/Sender", GTK_UI_MANAGER_MENUITEM)
7437 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/ReplyMode", "List", "Options/ReplyMode/List", GTK_UI_MANAGER_MENUITEM)
7439 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator1", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7440 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "PrivacySystem", "Options/PrivacySystem", GTK_UI_MANAGER_MENU)
7441 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/PrivacySystem", "PlaceHolder", "Options/PrivacySystem/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7442 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Sign", "Options/Sign", GTK_UI_MANAGER_MENUITEM)
7443 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encrypt", "Options/Encrypt", GTK_UI_MANAGER_MENUITEM)
7446 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator2", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7447 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Priority", "Options/Priority", GTK_UI_MANAGER_MENU)
7448 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Highest", "Options/Priority/Highest", GTK_UI_MANAGER_MENUITEM)
7449 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "High", "Options/Priority/High", GTK_UI_MANAGER_MENUITEM)
7450 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Normal", "Options/Priority/Normal", GTK_UI_MANAGER_MENUITEM)
7451 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Low", "Options/Priority/Low", GTK_UI_MANAGER_MENUITEM)
7452 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Priority", "Lowest", "Options/Priority/Lowest", GTK_UI_MANAGER_MENUITEM)
7454 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator3", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7455 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RequestRetRcpt", "Options/RequestRetRcpt", GTK_UI_MANAGER_MENUITEM)
7456 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator4", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7457 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "RemoveReferences", "Options/RemoveReferences", GTK_UI_MANAGER_MENUITEM)
7458 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Separator5", "Options/---", GTK_UI_MANAGER_SEPARATOR)
7460 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options", "Encoding", "Options/Encoding", GTK_UI_MANAGER_MENU)
7462 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_AUTO, "Options/Encoding/"CS_AUTO, GTK_UI_MANAGER_MENUITEM)
7463 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator1", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7464 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_US_ASCII, "Options/Encoding/"CS_US_ASCII, GTK_UI_MANAGER_MENUITEM)
7465 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_UTF_8, "Options/Encoding/"CS_UTF_8, GTK_UI_MANAGER_MENUITEM)
7466 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Separator2", "Options/Encoding/---", GTK_UI_MANAGER_SEPARATOR)
7468 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Western", "Options/Encoding/Western", GTK_UI_MANAGER_MENU)
7469 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)
7470 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)
7471 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Western", CS_WINDOWS_1252, "Options/Encoding/Western/"CS_WINDOWS_1252, GTK_UI_MANAGER_MENUITEM)
7473 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_2, "Options/Encoding/"CS_ISO_8859_2, GTK_UI_MANAGER_MENUITEM)
7475 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Baltic", "Options/Encoding/Baltic", GTK_UI_MANAGER_MENU)
7476 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)
7477 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)
7479 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_7, "Options/Encoding/"CS_ISO_8859_7, GTK_UI_MANAGER_MENUITEM)
7481 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Hebrew", "Options/Encoding/Hebrew", GTK_UI_MANAGER_MENU)
7482 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)
7483 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Hebrew", CS_WINDOWS_1255, "Options/Encoding/Hebrew/"CS_WINDOWS_1255, GTK_UI_MANAGER_MENUITEM)
7485 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Arabic", "Options/Encoding/Arabic", GTK_UI_MANAGER_MENU)
7486 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)
7487 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Arabic", CS_WINDOWS_1256, "Options/Encoding/Arabic/"CS_WINDOWS_1256, GTK_UI_MANAGER_MENUITEM)
7489 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", CS_ISO_8859_9, "Options/Encoding/"CS_ISO_8859_9, GTK_UI_MANAGER_MENUITEM)
7491 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Cyrillic", "Options/Encoding/Cyrillic", GTK_UI_MANAGER_MENU)
7492 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)
7493 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_R, "Options/Encoding/Cyrillic/"CS_KOI8_R, GTK_UI_MANAGER_MENUITEM)
7494 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_KOI8_U, "Options/Encoding/Cyrillic/"CS_KOI8_U, GTK_UI_MANAGER_MENUITEM)
7495 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Cyrillic", CS_WINDOWS_1251, "Options/Encoding/Cyrillic/"CS_WINDOWS_1251, GTK_UI_MANAGER_MENUITEM)
7497 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Japanese", "Options/Encoding/Japanese", GTK_UI_MANAGER_MENU)
7498 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)
7499 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)
7500 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_EUC_JP, "Options/Encoding/Japanese/"CS_EUC_JP, GTK_UI_MANAGER_MENUITEM)
7501 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Japanese", CS_SHIFT_JIS, "Options/Encoding/Japanese/"CS_SHIFT_JIS, GTK_UI_MANAGER_MENUITEM)
7503 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Chinese", "Options/Encoding/Chinese", GTK_UI_MANAGER_MENU)
7504 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB18030, "Options/Encoding/Chinese/"CS_GB18030, GTK_UI_MANAGER_MENUITEM)
7505 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GB2312, "Options/Encoding/Chinese/"CS_GB2312, GTK_UI_MANAGER_MENUITEM)
7506 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_GBK, "Options/Encoding/Chinese/"CS_GBK, GTK_UI_MANAGER_MENUITEM)
7507 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_BIG5, "Options/Encoding/Chinese/"CS_BIG5, GTK_UI_MANAGER_MENUITEM)
7508 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Chinese", CS_EUC_TW, "Options/Encoding/Chinese/"CS_EUC_TW, GTK_UI_MANAGER_MENUITEM)
7510 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Korean", "Options/Encoding/Korean", GTK_UI_MANAGER_MENU)
7511 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Korean", CS_EUC_KR, "Options/Encoding/Korean/"CS_EUC_KR, GTK_UI_MANAGER_MENUITEM)
7512 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)
7514 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding", "Thai", "Options/Encoding/Thai", GTK_UI_MANAGER_MENU)
7515 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_TIS_620, "Options/Encoding/Thai/"CS_TIS_620, GTK_UI_MANAGER_MENUITEM)
7516 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Options/Encoding/Thai", CS_WINDOWS_874, "Options/Encoding/Thai/"CS_WINDOWS_874, GTK_UI_MANAGER_MENUITEM)
7520 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "ShowRuler", "Tools/ShowRuler", GTK_UI_MANAGER_MENUITEM)
7521 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "AddressBook", "Tools/AddressBook", GTK_UI_MANAGER_MENUITEM)
7522 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Template", "Tools/Template", GTK_UI_MANAGER_MENU)
7523 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Template", "PlaceHolder", "Tools/Template/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7524 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "Actions", "Tools/Actions", GTK_UI_MANAGER_MENU)
7525 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools/Actions", "PlaceHolder", "Tools/Actions/PlaceHolder", GTK_UI_MANAGER_MENUITEM)
7528 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Help", "About", "Help/About", GTK_UI_MANAGER_MENUITEM)
7530 menubar = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu");
7531 gtk_widget_show_all(menubar);
7533 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(compose->ui_manager));
7535 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
7537 hildon_window_set_menu(HILDON_WINDOW(window), GTK_MENU(menubar));
7540 if (prefs_common.toolbar_detachable) {
7541 handlebox = gtk_handle_box_new();
7543 handlebox = gtk_hbox_new(FALSE, 0);
7545 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
7547 gtk_widget_realize(handlebox);
7549 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
7552 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
7556 vbox2 = gtk_vbox_new(FALSE, 2);
7557 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
7558 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
7561 notebook = gtk_notebook_new();
7562 gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
7563 gtk_widget_show(notebook);
7565 /* header labels and entries */
7566 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7567 compose_create_header(compose),
7568 gtk_label_new_with_mnemonic(_("Hea_der")));
7569 /* attachment list */
7570 attach_hbox = gtk_hbox_new(FALSE, 0);
7571 gtk_widget_show(attach_hbox);
7573 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
7574 gtk_widget_show(attach_lab1);
7575 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
7577 attach_lab2 = gtk_label_new("");
7578 gtk_widget_show(attach_lab2);
7579 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
7581 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7582 compose_create_attach(compose),
7585 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
7586 compose_create_others(compose),
7587 gtk_label_new_with_mnemonic(_("Othe_rs")));
7590 subject_hbox = gtk_hbox_new(FALSE, 0);
7591 gtk_widget_show(subject_hbox);
7593 subject_frame = gtk_frame_new(NULL);
7594 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
7595 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
7596 gtk_widget_show(subject_frame);
7598 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
7599 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
7600 gtk_widget_show(subject);
7602 label = gtk_label_new(_("Subject:"));
7603 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
7604 gtk_widget_show(label);
7607 subject_entry = claws_spell_entry_new();
7609 subject_entry = gtk_entry_new();
7611 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
7612 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
7613 G_CALLBACK(compose_grab_focus_cb), compose);
7614 gtk_widget_show(subject_entry);
7615 compose->subject_entry = subject_entry;
7616 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
7618 edit_vbox = gtk_vbox_new(FALSE, 0);
7620 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
7623 ruler_hbox = gtk_hbox_new(FALSE, 0);
7624 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
7626 ruler = gtk_shruler_new(GTK_ORIENTATION_HORIZONTAL);
7627 gtk_shruler_set_range(GTK_SHRULER(ruler), 0.0, 100.0, 1.0);
7628 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
7632 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
7633 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
7634 GTK_POLICY_AUTOMATIC,
7635 GTK_POLICY_AUTOMATIC);
7636 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
7638 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
7640 text = gtk_text_view_new();
7641 if (prefs_common.show_compose_margin) {
7642 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text), 6);
7643 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text), 6);
7645 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
7646 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
7647 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
7648 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7649 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
7651 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
7652 g_signal_connect(G_OBJECT(notebook), "size_allocate",
7653 G_CALLBACK(compose_notebook_size_alloc), compose);
7654 g_signal_connect_after(G_OBJECT(text), "size_allocate",
7655 G_CALLBACK(compose_edit_size_alloc),
7657 g_signal_connect(G_OBJECT(buffer), "changed",
7658 G_CALLBACK(compose_changed_cb), compose);
7659 g_signal_connect(G_OBJECT(text), "grab_focus",
7660 G_CALLBACK(compose_grab_focus_cb), compose);
7661 g_signal_connect(G_OBJECT(buffer), "insert_text",
7662 G_CALLBACK(text_inserted), compose);
7663 g_signal_connect(G_OBJECT(text), "button_press_event",
7664 G_CALLBACK(text_clicked), compose);
7666 g_signal_connect(G_OBJECT(text), "popup-menu",
7667 G_CALLBACK(compose_popup_menu), compose);
7669 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
7670 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
7671 g_signal_connect(G_OBJECT(text), "tap-and-hold",
7672 G_CALLBACK(compose_popup_menu), compose);
7674 g_signal_connect(G_OBJECT(subject_entry), "changed",
7675 G_CALLBACK(compose_changed_cb), compose);
7678 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
7679 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
7680 GDK_ACTION_COPY | GDK_ACTION_MOVE);
7681 g_signal_connect(G_OBJECT(text), "drag_data_received",
7682 G_CALLBACK(compose_insert_drag_received_cb),
7684 g_signal_connect(G_OBJECT(text), "drag-drop",
7685 G_CALLBACK(compose_drag_drop),
7687 g_signal_connect(G_OBJECT(text), "key-press-event",
7688 G_CALLBACK(completion_set_focus_to_subject),
7690 gtk_widget_show_all(vbox);
7692 /* pane between attach clist and text */
7693 paned = gtk_vpaned_new();
7694 gtk_container_add(GTK_CONTAINER(vbox2), paned);
7696 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
7697 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
7699 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
7701 gtk_paned_add1(GTK_PANED(paned), notebook);
7702 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
7703 gtk_widget_show_all(paned);
7706 if (prefs_common.textfont) {
7707 PangoFontDescription *font_desc;
7709 font_desc = pango_font_description_from_string
7710 (prefs_common.textfont);
7712 gtk_widget_modify_font(text, font_desc);
7713 pango_font_description_free(font_desc);
7717 gtk_action_group_add_actions(action_group, compose_popup_entries,
7718 G_N_ELEMENTS(compose_popup_entries), (gpointer)compose);
7719 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/", "Popup", NULL, GTK_UI_MANAGER_MENUBAR)
7720 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup", "Compose", "Compose", GTK_UI_MANAGER_MENU)
7721 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Add", "Compose/Add", GTK_UI_MANAGER_MENUITEM)
7722 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Remove", "Compose/Remove", GTK_UI_MANAGER_MENUITEM)
7723 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Separator1", "Compose/---", GTK_UI_MANAGER_SEPARATOR)
7724 MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Popup/Compose", "Properties", "Compose/Properties", GTK_UI_MANAGER_MENUITEM)
7726 popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtk_ui_manager_get_widget(compose->ui_manager, "/Popup/Compose")));
7728 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
7729 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
7730 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/RemoveReferences", FALSE);
7732 tmpl_menu = gtk_ui_manager_get_widget(compose->ui_manager, "/Menu/Tools/Template");
7734 undostruct = undo_init(text);
7735 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7738 address_completion_start(window);
7740 compose->window = window;
7741 compose->vbox = vbox;
7742 compose->menubar = menubar;
7743 compose->handlebox = handlebox;
7745 compose->vbox2 = vbox2;
7747 compose->paned = paned;
7749 compose->attach_label = attach_lab2;
7751 compose->notebook = notebook;
7752 compose->edit_vbox = edit_vbox;
7753 compose->ruler_hbox = ruler_hbox;
7754 compose->ruler = ruler;
7755 compose->scrolledwin = scrolledwin;
7756 compose->text = text;
7758 compose->focused_editable = NULL;
7760 compose->popupmenu = popupmenu;
7762 compose->tmpl_menu = tmpl_menu;
7764 compose->mode = mode;
7765 compose->rmode = mode;
7767 compose->targetinfo = NULL;
7768 compose->replyinfo = NULL;
7769 compose->fwdinfo = NULL;
7771 compose->email_hashtable = g_hash_table_new_full(g_str_hash,
7772 g_str_equal, (GDestroyNotify) g_free, NULL);
7774 compose->replyto = NULL;
7776 compose->bcc = NULL;
7777 compose->followup_to = NULL;
7779 compose->ml_post = NULL;
7781 compose->inreplyto = NULL;
7782 compose->references = NULL;
7783 compose->msgid = NULL;
7784 compose->boundary = NULL;
7786 compose->autowrap = prefs_common.autowrap;
7787 compose->autoindent = prefs_common.auto_indent;
7788 compose->use_signing = FALSE;
7789 compose->use_encryption = FALSE;
7790 compose->privacy_system = NULL;
7792 compose->modified = FALSE;
7794 compose->return_receipt = FALSE;
7796 compose->to_list = NULL;
7797 compose->newsgroup_list = NULL;
7799 compose->undostruct = undostruct;
7801 compose->sig_str = NULL;
7803 compose->exteditor_file = NULL;
7804 compose->exteditor_pid = -1;
7805 compose->exteditor_tag = -1;
7806 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7809 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
7810 if (mode != COMPOSE_REDIRECT) {
7811 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7812 strcmp(prefs_common.dictionary, "")) {
7813 gtkaspell = gtkaspell_new(prefs_common.dictionary,
7814 prefs_common.alt_dictionary,
7815 conv_get_locale_charset_str(),
7816 prefs_common.misspelled_col,
7817 prefs_common.check_while_typing,
7818 prefs_common.recheck_when_changing_dict,
7819 prefs_common.use_alternate,
7820 prefs_common.use_both_dicts,
7821 GTK_TEXT_VIEW(text),
7822 GTK_WINDOW(compose->window),
7823 compose_dict_changed,
7824 compose_spell_menu_changed,
7827 alertpanel_error(_("Spell checker could not "
7829 gtkaspell_checkers_strerror());
7830 gtkaspell_checkers_reset_error();
7832 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", TRUE);
7836 compose->gtkaspell = gtkaspell;
7837 compose_spell_menu_changed(compose);
7838 claws_spell_entry_set_gtkaspell(CLAWS_SPELL_ENTRY(subject_entry), gtkaspell);
7841 compose_select_account(compose, account, TRUE);
7843 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoWrap", prefs_common.autowrap);
7844 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Edit/AutoIndent", prefs_common.auto_indent);
7846 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7847 compose_entry_append(compose, account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
7849 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7850 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
7852 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7853 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
7855 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/ReplyMode", compose->mode == COMPOSE_REPLY);
7856 if (account->protocol != A_NNTP)
7857 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7858 prefs_common_translated_header_name("To:"));
7860 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))),
7861 prefs_common_translated_header_name("Newsgroups:"));
7863 #ifndef USE_NEW_ADDRBOOK
7864 addressbook_set_target_compose(compose);
7866 if (mode != COMPOSE_REDIRECT)
7867 compose_set_template_menu(compose);
7869 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools/Template", FALSE);
7872 compose_list = g_list_append(compose_list, compose);
7874 if (!prefs_common.show_ruler)
7875 gtk_widget_hide(ruler_hbox);
7877 cm_toggle_menu_set_active_full(compose->ui_manager, "Menu/Tools/ShowRuler", prefs_common.show_ruler);
7880 compose->priority = PRIORITY_NORMAL;
7881 compose_update_priority_menu_item(compose);
7883 compose_set_out_encoding(compose);
7886 compose_update_actions_menu(compose);
7888 /* Privacy Systems menu */
7889 compose_update_privacy_systems_menu(compose);
7891 activate_privacy_system(compose, account, TRUE);
7892 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7894 gtk_widget_realize(window);
7896 gtk_widget_show(window);
7898 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7899 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7906 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7911 GtkWidget *optmenubox;
7914 GtkWidget *from_name = NULL;
7915 #if !(GTK_CHECK_VERSION(2,12,0))
7916 GtkTooltips *tips = compose->tooltips;
7919 gint num = 0, def_menu = 0;
7921 accounts = account_get_list();
7922 cm_return_val_if_fail(accounts != NULL, NULL);
7924 optmenubox = gtk_event_box_new();
7925 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7926 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7928 hbox = gtk_hbox_new(FALSE, 6);
7929 from_name = gtk_entry_new();
7931 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7932 G_CALLBACK(compose_grab_focus_cb), compose);
7934 for (; accounts != NULL; accounts = accounts->next, num++) {
7935 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7936 gchar *name, *from = NULL;
7938 if (ac == compose->account) def_menu = num;
7940 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7943 if (ac == compose->account) {
7944 if (ac->name && *ac->name) {
7946 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7947 from = g_strdup_printf("%s <%s>",
7949 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7951 from = g_strdup_printf("%s",
7953 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7956 COMBOBOX_ADD(menu, name, ac->account_id);
7961 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7963 g_signal_connect(G_OBJECT(optmenu), "changed",
7964 G_CALLBACK(account_activated),
7966 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7967 G_CALLBACK(compose_entry_popup_extend),
7970 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7971 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7973 CLAWS_SET_TIP(optmenubox,
7974 _("Account to use for this email"));
7975 CLAWS_SET_TIP(from_name,
7976 _("Sender address to be used"));
7978 compose->account_combo = optmenu;
7979 compose->from_name = from_name;
7984 static void compose_set_priority_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
7986 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
7987 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
7988 Compose *compose = (Compose *) data;
7990 compose->priority = value;
7994 static void compose_reply_change_mode(Compose *compose,
7997 gboolean was_modified = compose->modified;
7999 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
8001 cm_return_if_fail(compose->replyinfo != NULL);
8003 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
8005 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
8007 if (action == COMPOSE_REPLY_TO_ALL)
8009 if (action == COMPOSE_REPLY_TO_SENDER)
8011 if (action == COMPOSE_REPLY_TO_LIST)
8014 compose_remove_header_entries(compose);
8015 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
8016 if (compose->account->set_autocc && compose->account->auto_cc)
8017 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC, PREF_ACCOUNT);
8019 if (compose->account->set_autobcc && compose->account->auto_bcc)
8020 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC, PREF_ACCOUNT);
8022 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
8023 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO, PREF_ACCOUNT);
8024 compose_show_first_last_header(compose, TRUE);
8025 compose->modified = was_modified;
8026 compose_set_title(compose);
8029 static void compose_reply_change_mode_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
8031 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
8032 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
8033 Compose *compose = (Compose *) data;
8036 compose_reply_change_mode(compose, value);
8039 static void compose_update_priority_menu_item(Compose * compose)
8041 GtkWidget *menuitem = NULL;
8042 switch (compose->priority) {
8043 case PRIORITY_HIGHEST:
8044 menuitem = gtk_ui_manager_get_widget
8045 (compose->ui_manager, "/Menu/Options/Priority/Highest");
8048 menuitem = gtk_ui_manager_get_widget
8049 (compose->ui_manager, "/Menu/Options/Priority/High");
8051 case PRIORITY_NORMAL:
8052 menuitem = gtk_ui_manager_get_widget
8053 (compose->ui_manager, "/Menu/Options/Priority/Normal");
8056 menuitem = gtk_ui_manager_get_widget
8057 (compose->ui_manager, "/Menu/Options/Priority/Low");
8059 case PRIORITY_LOWEST:
8060 menuitem = gtk_ui_manager_get_widget
8061 (compose->ui_manager, "/Menu/Options/Priority/Lowest");
8064 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8067 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
8069 Compose *compose = (Compose *) data;
8071 gboolean can_sign = FALSE, can_encrypt = FALSE;
8073 cm_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
8075 if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
8078 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
8079 g_free(compose->privacy_system);
8080 compose->privacy_system = NULL;
8081 if (systemid != NULL) {
8082 compose->privacy_system = g_strdup(systemid);
8084 can_sign = privacy_system_can_sign(systemid);
8085 can_encrypt = privacy_system_can_encrypt(systemid);
8088 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
8090 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8091 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8094 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
8096 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8097 GtkWidget *menuitem = NULL;
8098 GList *children, *amenu;
8099 gboolean can_sign = FALSE, can_encrypt = FALSE;
8100 gboolean found = FALSE;
8102 if (compose->privacy_system != NULL) {
8104 menuitem = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
8105 gtk_ui_manager_get_widget(compose->ui_manager, branch_path)));
8106 cm_return_if_fail(menuitem != NULL);
8108 children = gtk_container_get_children(GTK_CONTAINER(GTK_MENU_SHELL(menuitem)));
8111 while (amenu != NULL) {
8112 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
8113 if (systemid != NULL) {
8114 if (strcmp(systemid, compose->privacy_system) == 0 &&
8115 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8116 menuitem = GTK_WIDGET(amenu->data);
8118 can_sign = privacy_system_can_sign(systemid);
8119 can_encrypt = privacy_system_can_encrypt(systemid);
8123 } else if (strlen(compose->privacy_system) == 0 &&
8124 GTK_IS_CHECK_MENU_ITEM(amenu->data)) {
8125 menuitem = GTK_WIDGET(amenu->data);
8128 can_encrypt = FALSE;
8133 amenu = amenu->next;
8135 g_list_free(children);
8136 if (menuitem != NULL)
8137 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
8139 if (warn && !found && strlen(compose->privacy_system)) {
8140 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
8141 "will not be able to sign or encrypt this message."),
8142 compose->privacy_system);
8146 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Sign", can_sign);
8147 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options/Encrypt", can_encrypt);
8150 static void compose_set_out_encoding(Compose *compose)
8152 CharSet out_encoding;
8153 const gchar *branch = NULL;
8154 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
8156 switch(out_encoding) {
8157 case C_AUTO: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8158 case C_US_ASCII: branch = "Menu/Options/Encoding/" CS_US_ASCII; break;
8159 case C_UTF_8: branch = "Menu/Options/Encoding/" CS_UTF_8; break;
8160 case C_ISO_8859_2: branch = "Menu/Options/Encoding/" CS_ISO_8859_2; break;
8161 case C_ISO_8859_7: branch = "Menu/Options/Encoding/" CS_ISO_8859_7; break;
8162 case C_ISO_8859_9: branch = "Menu/Options/Encoding/" CS_ISO_8859_9; break;
8163 case C_ISO_8859_1: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_1; break;
8164 case C_ISO_8859_15: branch = "Menu/Options/Encoding/Western/" CS_ISO_8859_15; break;
8165 case C_WINDOWS_1252: branch = "Menu/Options/Encoding/Western/" CS_WINDOWS_1252; break;
8166 case C_ISO_8859_13: branch = "Menu/Options/Encoding/Baltic/" CS_ISO_8859_13; break;
8167 case C_ISO_8859_4: branch = "Menu/Options/Encoding/Baltic" CS_ISO_8859_4; break;
8168 case C_ISO_8859_8: branch = "Menu/Options/Encoding/Hebrew/" CS_ISO_8859_8; break;
8169 case C_WINDOWS_1255: branch = "Menu/Options/Encoding/Hebrew/" CS_WINDOWS_1255; break;
8170 case C_ISO_8859_6: branch = "Menu/Options/Encoding/Arabic/" CS_ISO_8859_6; break;
8171 case C_WINDOWS_1256: branch = "Menu/Options/Encoding/Arabic/" CS_WINDOWS_1256; break;
8172 case C_ISO_8859_5: branch = "Menu/Options/Encoding/Cyrillic/" CS_ISO_8859_5; break;
8173 case C_KOI8_R: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_R; break;
8174 case C_KOI8_U: branch = "Menu/Options/Encoding/Cyrillic/" CS_KOI8_U; break;
8175 case C_WINDOWS_1251: branch = "Menu/Options/Encoding/Cyrillic/" CS_WINDOWS_1251; break;
8176 case C_ISO_2022_JP: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP; break;
8177 case C_ISO_2022_JP_2: branch = "Menu/Options/Encoding/Japanese/" CS_ISO_2022_JP_2; break;
8178 case C_EUC_JP: branch = "Menu/Options/Encoding/Japanese/" CS_EUC_JP; break;
8179 case C_SHIFT_JIS: branch = "Menu/Options/Encoding/Japanese/" CS_SHIFT_JIS; break;
8180 case C_GB18030: branch = "Menu/Options/Encoding/Chinese/" CS_GB18030; break;
8181 case C_GB2312: branch = "Menu/Options/Encoding/Chinese/" CS_GB2312; break;
8182 case C_GBK: branch = "Menu/Options/Encoding/Chinese/" CS_GBK; break;
8183 case C_BIG5: branch = "Menu/Options/Encoding/Chinese/" CS_BIG5; break;
8184 case C_EUC_TW: branch = "Menu/Options/Encoding/Chinese/" CS_EUC_TW; break;
8185 case C_EUC_KR: branch = "Menu/Options/Encoding/Korean/" CS_EUC_KR; break;
8186 case C_ISO_2022_KR: branch = "Menu/Options/Encoding/Korean/" CS_ISO_2022_KR; break;
8187 case C_TIS_620: branch = "Menu/Options/Encoding/Thai/" CS_TIS_620; break;
8188 case C_WINDOWS_874: branch = "Menu/Options/Encoding/Thai/" CS_WINDOWS_874; break;
8189 default: branch = "Menu/Options/Encoding/" CS_AUTO; break;
8191 cm_toggle_menu_set_active_full(compose->ui_manager, (gchar *)branch, TRUE);
8194 static void compose_set_template_menu(Compose *compose)
8196 GSList *tmpl_list, *cur;
8200 tmpl_list = template_get_config();
8202 menu = gtk_menu_new();
8204 gtk_menu_set_accel_group (GTK_MENU (menu),
8205 gtk_ui_manager_get_accel_group(compose->ui_manager));
8206 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
8207 Template *tmpl = (Template *)cur->data;
8208 gchar *accel_path = NULL;
8209 item = gtk_menu_item_new_with_label(tmpl->name);
8210 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
8211 g_signal_connect(G_OBJECT(item), "activate",
8212 G_CALLBACK(compose_template_activate_cb),
8214 g_object_set_data(G_OBJECT(item), "template", tmpl);
8215 gtk_widget_show(item);
8216 accel_path = g_strconcat("<ComposeTemplates>" , "/", tmpl->name, NULL);
8217 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(item), accel_path);
8221 gtk_widget_show(menu);
8222 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
8225 void compose_update_actions_menu(Compose *compose)
8227 action_update_compose_menu(compose->ui_manager, "/Menu/Tools/Actions", compose);
8230 static void compose_update_privacy_systems_menu(Compose *compose)
8232 static gchar *branch_path = "/Menu/Options/PrivacySystem";
8233 GSList *systems, *cur;
8235 GtkWidget *system_none;
8237 GtkWidget *privacy_menuitem = gtk_ui_manager_get_widget(compose->ui_manager, branch_path);
8238 GtkWidget *privacy_menu = gtk_menu_new();
8240 system_none = gtk_radio_menu_item_new_with_mnemonic(NULL, _("_None"));
8241 g_object_set_data_full(G_OBJECT(system_none), "privacy_system", NULL, NULL);
8243 g_signal_connect(G_OBJECT(system_none), "activate",
8244 G_CALLBACK(compose_set_privacy_system_cb), compose);
8246 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), system_none);
8247 gtk_widget_show(system_none);
8249 systems = privacy_get_system_ids();
8250 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
8251 gchar *systemid = cur->data;
8253 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
8254 widget = gtk_radio_menu_item_new_with_label(group,
8255 privacy_system_get_name(systemid));
8256 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
8257 g_strdup(systemid), g_free);
8258 g_signal_connect(G_OBJECT(widget), "activate",
8259 G_CALLBACK(compose_set_privacy_system_cb), compose);
8261 gtk_menu_shell_append(GTK_MENU_SHELL(privacy_menu), widget);
8262 gtk_widget_show(widget);
8265 g_slist_free(systems);
8266 gtk_menu_item_set_submenu(GTK_MENU_ITEM(privacy_menuitem), privacy_menu);
8267 gtk_widget_show_all(privacy_menu);
8268 gtk_widget_show_all(privacy_menuitem);
8271 void compose_reflect_prefs_all(void)
8276 for (cur = compose_list; cur != NULL; cur = cur->next) {
8277 compose = (Compose *)cur->data;
8278 compose_set_template_menu(compose);
8282 void compose_reflect_prefs_pixmap_theme(void)
8287 for (cur = compose_list; cur != NULL; cur = cur->next) {
8288 compose = (Compose *)cur->data;
8289 toolbar_update(TOOLBAR_COMPOSE, compose);
8293 static const gchar *compose_quote_char_from_context(Compose *compose)
8295 const gchar *qmark = NULL;
8297 cm_return_val_if_fail(compose != NULL, NULL);
8299 switch (compose->mode) {
8300 /* use forward-specific quote char */
8301 case COMPOSE_FORWARD:
8302 case COMPOSE_FORWARD_AS_ATTACH:
8303 case COMPOSE_FORWARD_INLINE:
8304 if (compose->folder && compose->folder->prefs &&
8305 compose->folder->prefs->forward_with_format)
8306 qmark = compose->folder->prefs->forward_quotemark;
8307 else if (compose->account->forward_with_format)
8308 qmark = compose->account->forward_quotemark;
8310 qmark = prefs_common.fw_quotemark;
8313 /* use reply-specific quote char in all other modes */
8315 if (compose->folder && compose->folder->prefs &&
8316 compose->folder->prefs->reply_with_format)
8317 qmark = compose->folder->prefs->reply_quotemark;
8318 else if (compose->account->reply_with_format)
8319 qmark = compose->account->reply_quotemark;
8321 qmark = prefs_common.quotemark;
8325 if (qmark == NULL || *qmark == '\0')
8331 static void compose_template_apply(Compose *compose, Template *tmpl,
8335 GtkTextBuffer *buffer;
8339 gchar *parsed_str = NULL;
8340 gint cursor_pos = 0;
8341 const gchar *err_msg = _("The body of the template has an error at line %d.");
8344 /* process the body */
8346 text = GTK_TEXT_VIEW(compose->text);
8347 buffer = gtk_text_view_get_buffer(text);
8350 qmark = compose_quote_char_from_context(compose);
8352 if (compose->replyinfo != NULL) {
8355 gtk_text_buffer_set_text(buffer, "", -1);
8356 mark = gtk_text_buffer_get_insert(buffer);
8357 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8359 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
8360 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8362 } else if (compose->fwdinfo != NULL) {
8365 gtk_text_buffer_set_text(buffer, "", -1);
8366 mark = gtk_text_buffer_get_insert(buffer);
8367 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8369 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
8370 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
8373 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
8375 GtkTextIter start, end;
8378 gtk_text_buffer_get_start_iter(buffer, &start);
8379 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
8380 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
8382 /* clear the buffer now */
8384 gtk_text_buffer_set_text(buffer, "", -1);
8386 parsed_str = compose_quote_fmt(compose, dummyinfo,
8387 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
8388 procmsg_msginfo_free( dummyinfo );
8394 gtk_text_buffer_set_text(buffer, "", -1);
8395 mark = gtk_text_buffer_get_insert(buffer);
8396 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
8399 if (replace && parsed_str && compose->account->auto_sig)
8400 compose_insert_sig(compose, FALSE);
8402 if (replace && parsed_str) {
8403 gtk_text_buffer_get_start_iter(buffer, &iter);
8404 gtk_text_buffer_place_cursor(buffer, &iter);
8408 cursor_pos = quote_fmt_get_cursor_pos();
8409 compose->set_cursor_pos = cursor_pos;
8410 if (cursor_pos == -1)
8412 gtk_text_buffer_get_start_iter(buffer, &iter);
8413 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
8414 gtk_text_buffer_place_cursor(buffer, &iter);
8417 /* process the other fields */
8419 compose_template_apply_fields(compose, tmpl);
8420 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
8421 quote_fmt_reset_vartable();
8422 compose_changed_cb(NULL, compose);
8425 if (compose->gtkaspell && compose->gtkaspell->check_while_typing)
8426 gtkaspell_highlight_all(compose->gtkaspell);
8430 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
8432 MsgInfo* dummyinfo = NULL;
8433 MsgInfo *msginfo = NULL;
8436 if (compose->replyinfo != NULL)
8437 msginfo = compose->replyinfo;
8438 else if (compose->fwdinfo != NULL)
8439 msginfo = compose->fwdinfo;
8441 dummyinfo = compose_msginfo_new_from_compose(compose);
8442 msginfo = dummyinfo;
8445 if (tmpl->from && *tmpl->from != '\0') {
8447 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8448 compose->gtkaspell);
8450 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8452 quote_fmt_scan_string(tmpl->from);
8455 buf = quote_fmt_get_buffer();
8457 alertpanel_error(_("Template From format error."));
8459 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
8463 if (tmpl->to && *tmpl->to != '\0') {
8465 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8466 compose->gtkaspell);
8468 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8470 quote_fmt_scan_string(tmpl->to);
8473 buf = quote_fmt_get_buffer();
8475 alertpanel_error(_("Template To format error."));
8477 compose_entry_append(compose, buf, COMPOSE_TO, PREF_TEMPLATE);
8481 if (tmpl->cc && *tmpl->cc != '\0') {
8483 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8484 compose->gtkaspell);
8486 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8488 quote_fmt_scan_string(tmpl->cc);
8491 buf = quote_fmt_get_buffer();
8493 alertpanel_error(_("Template Cc format error."));
8495 compose_entry_append(compose, buf, COMPOSE_CC, PREF_TEMPLATE);
8499 if (tmpl->bcc && *tmpl->bcc != '\0') {
8501 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8502 compose->gtkaspell);
8504 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8506 quote_fmt_scan_string(tmpl->bcc);
8509 buf = quote_fmt_get_buffer();
8511 alertpanel_error(_("Template Bcc format error."));
8513 compose_entry_append(compose, buf, COMPOSE_BCC, PREF_TEMPLATE);
8517 /* process the subject */
8518 if (tmpl->subject && *tmpl->subject != '\0') {
8520 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
8521 compose->gtkaspell);
8523 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
8525 quote_fmt_scan_string(tmpl->subject);
8528 buf = quote_fmt_get_buffer();
8530 alertpanel_error(_("Template subject format error."));
8532 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
8536 procmsg_msginfo_free( dummyinfo );
8539 static void compose_destroy(Compose *compose)
8541 GtkAllocation allocation;
8542 GtkTextBuffer *buffer;
8543 GtkClipboard *clipboard;
8545 compose_list = g_list_remove(compose_list, compose);
8547 if (compose->updating) {
8548 debug_print("danger, not destroying anything now\n");
8549 compose->deferred_destroy = TRUE;
8552 /* NOTE: address_completion_end() does nothing with the window
8553 * however this may change. */
8554 address_completion_end(compose->window);
8556 slist_free_strings_full(compose->to_list);
8557 slist_free_strings_full(compose->newsgroup_list);
8558 slist_free_strings_full(compose->header_list);
8560 slist_free_strings_full(extra_headers);
8561 extra_headers = NULL;
8563 compose->header_list = compose->newsgroup_list = compose->to_list = NULL;
8565 g_hash_table_destroy(compose->email_hashtable);
8567 procmsg_msginfo_free(compose->targetinfo);
8568 procmsg_msginfo_free(compose->replyinfo);
8569 procmsg_msginfo_free(compose->fwdinfo);
8571 g_free(compose->replyto);
8572 g_free(compose->cc);
8573 g_free(compose->bcc);
8574 g_free(compose->newsgroups);
8575 g_free(compose->followup_to);
8577 g_free(compose->ml_post);
8579 g_free(compose->inreplyto);
8580 g_free(compose->references);
8581 g_free(compose->msgid);
8582 g_free(compose->boundary);
8584 g_free(compose->redirect_filename);
8585 if (compose->undostruct)
8586 undo_destroy(compose->undostruct);
8588 g_free(compose->sig_str);
8590 g_free(compose->exteditor_file);
8592 g_free(compose->orig_charset);
8594 g_free(compose->privacy_system);
8596 #ifndef USE_NEW_ADDRBOOK
8597 if (addressbook_get_target_compose() == compose)
8598 addressbook_set_target_compose(NULL);
8601 if (compose->gtkaspell) {
8602 gtkaspell_delete(compose->gtkaspell);
8603 compose->gtkaspell = NULL;
8607 if (!compose->batch) {
8608 gtk_widget_get_allocation(compose->window, &allocation);
8609 prefs_common.compose_width = allocation.width;
8610 prefs_common.compose_height = allocation.height;
8613 if (!gtk_widget_get_parent(compose->paned))
8614 gtk_widget_destroy(compose->paned);
8615 gtk_widget_destroy(compose->popupmenu);
8617 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
8618 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
8619 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
8621 gtk_widget_destroy(compose->window);
8622 toolbar_destroy(compose->toolbar);
8623 g_free(compose->toolbar);
8624 cm_mutex_free(compose->mutex);
8628 static void compose_attach_info_free(AttachInfo *ainfo)
8630 g_free(ainfo->file);
8631 g_free(ainfo->content_type);
8632 g_free(ainfo->name);
8633 g_free(ainfo->charset);
8637 static void compose_attach_update_label(Compose *compose)
8642 GtkTreeModel *model;
8647 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
8648 if(!gtk_tree_model_get_iter_first(model, &iter)) {
8649 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
8653 while(gtk_tree_model_iter_next(model, &iter))
8656 text = g_strdup_printf("(%d)", i);
8657 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
8661 static void compose_attach_remove_selected(GtkAction *action, gpointer data)
8663 Compose *compose = (Compose *)data;
8664 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8665 GtkTreeSelection *selection;
8667 GtkTreeModel *model;
8669 selection = gtk_tree_view_get_selection(tree_view);
8670 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8675 for (cur = sel; cur != NULL; cur = cur->next) {
8676 GtkTreePath *path = cur->data;
8677 GtkTreeRowReference *ref = gtk_tree_row_reference_new
8680 gtk_tree_path_free(path);
8683 for (cur = sel; cur != NULL; cur = cur->next) {
8684 GtkTreeRowReference *ref = cur->data;
8685 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
8688 if (gtk_tree_model_get_iter(model, &iter, path))
8689 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
8691 gtk_tree_path_free(path);
8692 gtk_tree_row_reference_free(ref);
8696 compose_attach_update_label(compose);
8699 static struct _AttachProperty
8702 GtkWidget *mimetype_entry;
8703 GtkWidget *encoding_optmenu;
8704 GtkWidget *path_entry;
8705 GtkWidget *filename_entry;
8707 GtkWidget *cancel_btn;
8710 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
8712 gtk_tree_path_free((GtkTreePath *)ptr);
8715 static void compose_attach_property(GtkAction *action, gpointer data)
8717 Compose *compose = (Compose *)data;
8718 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
8720 GtkComboBox *optmenu;
8721 GtkTreeSelection *selection;
8723 GtkTreeModel *model;
8726 static gboolean cancelled;
8728 /* only if one selected */
8729 selection = gtk_tree_view_get_selection(tree_view);
8730 if (gtk_tree_selection_count_selected_rows(selection) != 1)
8733 sel = gtk_tree_selection_get_selected_rows(selection, &model);
8737 path = (GtkTreePath *) sel->data;
8738 gtk_tree_model_get_iter(model, &iter, path);
8739 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
8742 g_list_foreach(sel, gtk_tree_path_free_, NULL);
8748 if (!attach_prop.window)
8749 compose_attach_property_create(&cancelled);
8750 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), TRUE);
8751 gtk_widget_grab_focus(attach_prop.ok_btn);
8752 gtk_widget_show(attach_prop.window);
8753 gtk_window_set_transient_for(GTK_WINDOW(attach_prop.window),
8754 GTK_WINDOW(compose->window));
8756 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8757 if (ainfo->encoding == ENC_UNKNOWN)
8758 combobox_select_by_data(optmenu, ENC_BASE64);
8760 combobox_select_by_data(optmenu, ainfo->encoding);
8762 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8763 ainfo->content_type ? ainfo->content_type : "");
8764 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8765 ainfo->file ? ainfo->file : "");
8766 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8767 ainfo->name ? ainfo->name : "");
8770 const gchar *entry_text;
8772 gchar *cnttype = NULL;
8779 gtk_widget_hide(attach_prop.window);
8780 gtk_window_set_modal(GTK_WINDOW(attach_prop.window), FALSE);
8785 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8786 if (*entry_text != '\0') {
8789 text = g_strstrip(g_strdup(entry_text));
8790 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8791 cnttype = g_strdup(text);
8794 alertpanel_error(_("Invalid MIME type."));
8800 ainfo->encoding = combobox_get_active_data(optmenu);
8802 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8803 if (*entry_text != '\0') {
8804 if (is_file_exist(entry_text) &&
8805 (size = get_file_size(entry_text)) > 0)
8806 file = g_strdup(entry_text);
8809 (_("File doesn't exist or is empty."));
8815 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8816 if (*entry_text != '\0') {
8817 g_free(ainfo->name);
8818 ainfo->name = g_strdup(entry_text);
8822 g_free(ainfo->content_type);
8823 ainfo->content_type = cnttype;
8826 g_free(ainfo->file);
8830 ainfo->size = (goffset)size;
8832 /* update tree store */
8833 text = to_human_readable(ainfo->size);
8834 gtk_tree_model_get_iter(model, &iter, path);
8835 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8836 COL_MIMETYPE, ainfo->content_type,
8838 COL_NAME, ainfo->name,
8839 COL_CHARSET, ainfo->charset,
8845 gtk_tree_path_free(path);
8848 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8850 label = gtk_label_new(str); \
8851 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8852 GTK_FILL, 0, 0, 0); \
8853 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8855 entry = gtk_entry_new(); \
8856 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8857 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8860 static void compose_attach_property_create(gboolean *cancelled)
8866 GtkWidget *mimetype_entry;
8869 GtkListStore *optmenu_menu;
8870 GtkWidget *path_entry;
8871 GtkWidget *filename_entry;
8874 GtkWidget *cancel_btn;
8875 GList *mime_type_list, *strlist;
8878 debug_print("Creating attach_property window...\n");
8880 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8881 gtk_widget_set_size_request(window, 480, -1);
8882 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8883 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8884 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8885 g_signal_connect(G_OBJECT(window), "delete_event",
8886 G_CALLBACK(attach_property_delete_event),
8888 g_signal_connect(G_OBJECT(window), "key_press_event",
8889 G_CALLBACK(attach_property_key_pressed),
8892 vbox = gtk_vbox_new(FALSE, 8);
8893 gtk_container_add(GTK_CONTAINER(window), vbox);
8895 table = gtk_table_new(4, 2, FALSE);
8896 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8897 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8898 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8900 label = gtk_label_new(_("MIME type"));
8901 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8903 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8904 #if !GTK_CHECK_VERSION(2, 24, 0)
8905 mimetype_entry = gtk_combo_box_entry_new_text();
8907 mimetype_entry = gtk_combo_box_text_new_with_entry();
8909 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8910 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8912 /* stuff with list */
8913 mime_type_list = procmime_get_mime_type_list();
8915 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8916 MimeType *type = (MimeType *) mime_type_list->data;
8919 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8921 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8924 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8925 (GCompareFunc)strcmp2);
8928 for (mime_type_list = strlist; mime_type_list != NULL;
8929 mime_type_list = mime_type_list->next) {
8930 #if !GTK_CHECK_VERSION(2, 24, 0)
8931 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8933 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(mimetype_entry), mime_type_list->data);
8935 g_free(mime_type_list->data);
8937 g_list_free(strlist);
8938 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8939 mimetype_entry = gtk_bin_get_child(GTK_BIN((mimetype_entry)));
8941 label = gtk_label_new(_("Encoding"));
8942 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8944 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8946 hbox = gtk_hbox_new(FALSE, 0);
8947 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8948 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8950 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8951 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8953 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8954 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8955 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8956 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8957 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8959 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8961 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8962 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8964 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8965 &ok_btn, GTK_STOCK_OK,
8967 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8968 gtk_widget_grab_default(ok_btn);
8970 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8971 G_CALLBACK(attach_property_ok),
8973 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8974 G_CALLBACK(attach_property_cancel),
8977 gtk_widget_show_all(vbox);
8979 attach_prop.window = window;
8980 attach_prop.mimetype_entry = mimetype_entry;
8981 attach_prop.encoding_optmenu = optmenu;
8982 attach_prop.path_entry = path_entry;
8983 attach_prop.filename_entry = filename_entry;
8984 attach_prop.ok_btn = ok_btn;
8985 attach_prop.cancel_btn = cancel_btn;
8988 #undef SET_LABEL_AND_ENTRY
8990 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8996 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
9002 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
9003 gboolean *cancelled)
9011 static gboolean attach_property_key_pressed(GtkWidget *widget,
9013 gboolean *cancelled)
9015 if (event && event->keyval == GDK_KEY_Escape) {
9019 if (event && event->keyval == GDK_KEY_Return) {
9027 static void compose_exec_ext_editor(Compose *compose)
9034 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
9035 G_DIR_SEPARATOR, compose);
9037 if (pipe(pipe_fds) < 0) {
9043 if ((pid = fork()) < 0) {
9050 /* close the write side of the pipe */
9053 compose->exteditor_file = g_strdup(tmp);
9054 compose->exteditor_pid = pid;
9056 compose_set_ext_editor_sensitive(compose, FALSE);
9059 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
9061 compose->exteditor_ch = g_io_channel_win32_new_fd(pipe_fds[0]);
9063 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
9067 } else { /* process-monitoring process */
9073 /* close the read side of the pipe */
9076 if (compose_write_body_to_file(compose, tmp) < 0) {
9077 fd_write_all(pipe_fds[1], "2\n", 2);
9081 pid_ed = compose_exec_ext_editor_real(tmp);
9083 fd_write_all(pipe_fds[1], "1\n", 2);
9087 /* wait until editor is terminated */
9088 waitpid(pid_ed, NULL, 0);
9090 fd_write_all(pipe_fds[1], "0\n", 2);
9097 #endif /* G_OS_UNIX */
9101 static gint compose_exec_ext_editor_real(const gchar *file)
9108 cm_return_val_if_fail(file != NULL, -1);
9110 if ((pid = fork()) < 0) {
9115 if (pid != 0) return pid;
9117 /* grandchild process */
9119 if (setpgid(0, getppid()))
9122 if (prefs_common_get_ext_editor_cmd() &&
9123 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
9124 *(p + 1) == 's' && !strchr(p + 2, '%')) {
9125 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
9127 if (prefs_common_get_ext_editor_cmd())
9128 g_warning("External editor command-line is invalid: '%s'\n",
9129 prefs_common_get_ext_editor_cmd());
9130 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
9133 cmdline = strsplit_with_quote(buf, " ", 1024);
9134 execvp(cmdline[0], cmdline);
9137 g_strfreev(cmdline);
9142 static gboolean compose_ext_editor_kill(Compose *compose)
9144 pid_t pgid = compose->exteditor_pid * -1;
9147 ret = kill(pgid, 0);
9149 if (ret == 0 || (ret == -1 && EPERM == errno)) {
9153 msg = g_strdup_printf
9154 (_("The external editor is still working.\n"
9155 "Force terminating the process?\n"
9156 "process group id: %d"), -pgid);
9157 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
9158 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
9162 if (val == G_ALERTALTERNATE) {
9163 g_source_remove(compose->exteditor_tag);
9164 g_io_channel_shutdown(compose->exteditor_ch,
9166 g_io_channel_unref(compose->exteditor_ch);
9168 if (kill(pgid, SIGTERM) < 0) perror("kill");
9169 waitpid(compose->exteditor_pid, NULL, 0);
9171 g_warning("Terminated process group id: %d", -pgid);
9172 g_warning("Temporary file: %s",
9173 compose->exteditor_file);
9175 compose_set_ext_editor_sensitive(compose, TRUE);
9177 g_free(compose->exteditor_file);
9178 compose->exteditor_file = NULL;
9179 compose->exteditor_pid = -1;
9180 compose->exteditor_ch = NULL;
9181 compose->exteditor_tag = -1;
9189 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
9193 Compose *compose = (Compose *)data;
9196 debug_print("Compose: input from monitoring process\n");
9198 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
9200 g_io_channel_shutdown(source, FALSE, NULL);
9201 g_io_channel_unref(source);
9203 waitpid(compose->exteditor_pid, NULL, 0);
9205 if (buf[0] == '0') { /* success */
9206 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9207 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
9209 gtk_text_buffer_set_text(buffer, "", -1);
9210 compose_insert_file(compose, compose->exteditor_file);
9211 compose_changed_cb(NULL, compose);
9212 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
9214 if (claws_unlink(compose->exteditor_file) < 0)
9215 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9216 } else if (buf[0] == '1') { /* failed */
9217 g_warning("Couldn't exec external editor\n");
9218 if (claws_unlink(compose->exteditor_file) < 0)
9219 FILE_OP_ERROR(compose->exteditor_file, "unlink");
9220 } else if (buf[0] == '2') {
9221 g_warning("Couldn't write to file\n");
9222 } else if (buf[0] == '3') {
9223 g_warning("Pipe read failed\n");
9226 compose_set_ext_editor_sensitive(compose, TRUE);
9228 g_free(compose->exteditor_file);
9229 compose->exteditor_file = NULL;
9230 compose->exteditor_pid = -1;
9231 compose->exteditor_ch = NULL;
9232 compose->exteditor_tag = -1;
9237 static void compose_set_ext_editor_sensitive(Compose *compose,
9240 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
9241 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
9242 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
9243 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
9244 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
9245 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
9246 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
9248 gtk_widget_set_sensitive(compose->text, sensitive);
9249 if (compose->toolbar->send_btn)
9250 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
9251 if (compose->toolbar->sendl_btn)
9252 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
9253 if (compose->toolbar->draft_btn)
9254 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
9255 if (compose->toolbar->insert_btn)
9256 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
9257 if (compose->toolbar->sig_btn)
9258 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
9259 if (compose->toolbar->exteditor_btn)
9260 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
9261 if (compose->toolbar->linewrap_current_btn)
9262 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
9263 if (compose->toolbar->linewrap_all_btn)
9264 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
9266 #endif /* G_OS_UNIX */
9269 * compose_undo_state_changed:
9271 * Change the sensivity of the menuentries undo and redo
9273 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
9274 gint redo_state, gpointer data)
9276 Compose *compose = (Compose *)data;
9278 switch (undo_state) {
9279 case UNDO_STATE_TRUE:
9280 if (!undostruct->undo_state) {
9281 undostruct->undo_state = TRUE;
9282 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", TRUE);
9285 case UNDO_STATE_FALSE:
9286 if (undostruct->undo_state) {
9287 undostruct->undo_state = FALSE;
9288 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", FALSE);
9291 case UNDO_STATE_UNCHANGED:
9293 case UNDO_STATE_REFRESH:
9294 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Undo", undostruct->undo_state);
9297 g_warning("Undo state not recognized");
9301 switch (redo_state) {
9302 case UNDO_STATE_TRUE:
9303 if (!undostruct->redo_state) {
9304 undostruct->redo_state = TRUE;
9305 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", TRUE);
9308 case UNDO_STATE_FALSE:
9309 if (undostruct->redo_state) {
9310 undostruct->redo_state = FALSE;
9311 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", FALSE);
9314 case UNDO_STATE_UNCHANGED:
9316 case UNDO_STATE_REFRESH:
9317 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/Redo", undostruct->redo_state);
9320 g_warning("Redo state not recognized");
9325 /* callback functions */
9327 static void compose_notebook_size_alloc(GtkNotebook *notebook,
9328 GtkAllocation *allocation,
9331 prefs_common.compose_notebook_height = allocation->height;
9334 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
9335 * includes "non-client" (windows-izm) in calculation, so this calculation
9336 * may not be accurate.
9338 static gboolean compose_edit_size_alloc(GtkEditable *widget,
9339 GtkAllocation *allocation,
9340 GtkSHRuler *shruler)
9342 if (prefs_common.show_ruler) {
9343 gint char_width = 0, char_height = 0;
9344 gint line_width_in_chars;
9346 gtkut_get_font_size(GTK_WIDGET(widget),
9347 &char_width, &char_height);
9348 line_width_in_chars =
9349 (allocation->width - allocation->x) / char_width;
9351 /* got the maximum */
9352 gtk_shruler_set_range(GTK_SHRULER(shruler),
9353 0.0, line_width_in_chars, 0);
9362 ComposePrefType type;
9363 gboolean entry_marked;
9366 static void account_activated(GtkComboBox *optmenu, gpointer data)
9368 Compose *compose = (Compose *)data;
9371 gchar *folderidentifier;
9372 gint account_id = 0;
9375 GSList *list, *saved_list = NULL;
9376 HeaderEntryState *state;
9377 GtkRcStyle *style = NULL;
9378 #if !GTK_CHECK_VERSION(3, 0, 0)
9379 static GdkColor yellow;
9380 static gboolean color_set = FALSE;
9382 static GdkColor yellow = { (guint32)0, (guint32)0xf5, (guint32)0xf6, (guint32)0xbe };
9385 /* Get ID of active account in the combo box */
9386 menu = gtk_combo_box_get_model(optmenu);
9387 gtk_combo_box_get_active_iter(optmenu, &iter);
9388 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
9390 ac = account_find_from_id(account_id);
9391 cm_return_if_fail(ac != NULL);
9393 if (ac != compose->account) {
9394 compose_select_account(compose, ac, FALSE);
9396 for (list = compose->header_list; list; list = list->next) {
9397 ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
9399 if (hentry->type == PREF_ACCOUNT || !list->next) {
9400 compose_destroy_headerentry(compose, hentry);
9404 state = g_malloc0(sizeof(HeaderEntryState));
9405 state->header = gtk_editable_get_chars(GTK_EDITABLE(
9406 gtk_bin_get_child(GTK_BIN(hentry->combo))), 0, -1);
9407 state->entry = gtk_editable_get_chars(
9408 GTK_EDITABLE(hentry->entry), 0, -1);
9409 state->type = hentry->type;
9411 #if !GTK_CHECK_VERSION(3, 0, 0)
9413 gdk_color_parse("#f5f6be", &yellow);
9414 color_set = gdk_colormap_alloc_color(
9415 gdk_colormap_get_system(),
9416 &yellow, FALSE, TRUE);
9420 style = gtk_widget_get_modifier_style(hentry->entry);
9421 state->entry_marked = gdk_color_equal(&yellow,
9422 &style->base[GTK_STATE_NORMAL]);
9424 saved_list = g_slist_append(saved_list, state);
9425 compose_destroy_headerentry(compose, hentry);
9428 compose->header_last = NULL;
9429 g_slist_free(compose->header_list);
9430 compose->header_list = NULL;
9431 compose->header_nextrow = 1;
9432 compose_create_header_entry(compose);
9434 if (ac->set_autocc && ac->auto_cc)
9435 compose_entry_append(compose, ac->auto_cc,
9436 COMPOSE_CC, PREF_ACCOUNT);
9438 if (ac->set_autobcc && ac->auto_bcc)
9439 compose_entry_append(compose, ac->auto_bcc,
9440 COMPOSE_BCC, PREF_ACCOUNT);
9442 if (ac->set_autoreplyto && ac->auto_replyto)
9443 compose_entry_append(compose, ac->auto_replyto,
9444 COMPOSE_REPLYTO, PREF_ACCOUNT);
9446 for (list = saved_list; list; list = list->next) {
9447 state = (HeaderEntryState *) list->data;
9449 compose_add_header_entry(compose, state->header,
9450 state->entry, state->type);
9451 if (state->entry_marked)
9452 compose_entry_mark_default_to(compose, state->entry);
9454 g_free(state->header);
9455 g_free(state->entry);
9458 g_slist_free(saved_list);
9460 combobox_select_by_data(GTK_COMBO_BOX(compose->header_last->combo),
9461 (ac->protocol == A_NNTP) ?
9462 COMPOSE_NEWSGROUPS : COMPOSE_TO);
9465 /* Set message save folder */
9466 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9467 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
9469 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
9470 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
9472 compose_set_save_to(compose, NULL);
9473 if (account_get_special_folder(compose->account, F_OUTBOX)) {
9474 folderidentifier = folder_item_get_identifier(account_get_special_folder
9475 (compose->account, F_OUTBOX));
9476 compose_set_save_to(compose, folderidentifier);
9477 g_free(folderidentifier);
9481 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
9482 GtkTreeViewColumn *column, Compose *compose)
9484 compose_attach_property(NULL, compose);
9487 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
9490 Compose *compose = (Compose *)data;
9491 GtkTreeSelection *attach_selection;
9492 gint attach_nr_selected;
9494 if (!event) return FALSE;
9496 if (event->button == 3) {
9497 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
9498 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
9500 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
9501 cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
9503 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
9504 NULL, NULL, event->button, event->time);
9511 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
9514 Compose *compose = (Compose *)data;
9516 if (!event) return FALSE;
9518 switch (event->keyval) {
9519 case GDK_KEY_Delete:
9520 compose_attach_remove_selected(NULL, compose);
9526 static void compose_allow_user_actions (Compose *compose, gboolean allow)
9528 toolbar_comp_set_sensitive(compose, allow);
9529 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message", allow);
9530 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit", allow);
9532 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", allow);
9534 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Options", allow);
9535 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Tools", allow);
9536 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Help", allow);
9538 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
9542 static void compose_send_cb(GtkAction *action, gpointer data)
9544 Compose *compose = (Compose *)data;
9546 if (prefs_common.work_offline &&
9547 !inc_offline_should_override(TRUE,
9548 _("Claws Mail needs network access in order "
9549 "to send this email.")))
9552 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
9553 g_source_remove(compose->draft_timeout_tag);
9554 compose->draft_timeout_tag = -1;
9557 compose_send(compose);
9560 static void compose_send_later_cb(GtkAction *action, gpointer data)
9562 Compose *compose = (Compose *)data;
9566 compose_allow_user_actions(compose, FALSE);
9567 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
9568 compose_allow_user_actions(compose, TRUE);
9572 compose_close(compose);
9573 } else if (val == -1) {
9574 alertpanel_error(_("Could not queue message."));
9575 } else if (val == -2) {
9576 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
9577 } else if (val == -3) {
9578 if (privacy_peek_error())
9579 alertpanel_error(_("Could not queue message for sending:\n\n"
9580 "Signature failed: %s"), privacy_get_error());
9581 } else if (val == -4) {
9582 alertpanel_error(_("Could not queue message for sending:\n\n"
9583 "Charset conversion failed."));
9584 } else if (val == -5) {
9585 alertpanel_error(_("Could not queue message for sending:\n\n"
9586 "Couldn't get recipient encryption key."));
9587 } else if (val == -6) {
9590 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
9593 #define DRAFTED_AT_EXIT "drafted_at_exit"
9594 static void compose_register_draft(MsgInfo *info)
9596 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9597 DRAFTED_AT_EXIT, NULL);
9598 FILE *fp = g_fopen(filepath, "ab");
9601 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
9609 gboolean compose_draft (gpointer data, guint action)
9611 Compose *compose = (Compose *)data;
9616 MsgFlags flag = {0, 0};
9617 static gboolean lock = FALSE;
9618 MsgInfo *newmsginfo;
9620 gboolean target_locked = FALSE;
9621 gboolean err = FALSE;
9623 if (lock) return FALSE;
9625 if (compose->sending)
9628 draft = account_get_special_folder(compose->account, F_DRAFT);
9629 cm_return_val_if_fail(draft != NULL, FALSE);
9631 if (!g_mutex_trylock(compose->mutex)) {
9632 /* we don't want to lock the mutex once it's available,
9633 * because as the only other part of compose.c locking
9634 * it is compose_close - which means once unlocked,
9635 * the compose struct will be freed */
9636 debug_print("couldn't lock mutex, probably sending\n");
9642 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
9643 G_DIR_SEPARATOR, compose);
9644 if ((fp = g_fopen(tmp, "wb")) == NULL) {
9645 FILE_OP_ERROR(tmp, "fopen");
9649 /* chmod for security */
9650 if (change_file_mode_rw(fp, tmp) < 0) {
9651 FILE_OP_ERROR(tmp, "chmod");
9652 g_warning("can't change file mode\n");
9655 /* Save draft infos */
9656 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
9657 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
9659 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
9660 gchar *savefolderid;
9662 savefolderid = compose_get_save_to(compose);
9663 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
9664 g_free(savefolderid);
9666 if (compose->return_receipt) {
9667 err |= (fprintf(fp, "RRCPT:1\n") < 0);
9669 if (compose->privacy_system) {
9670 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
9671 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
9672 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
9675 /* Message-ID of message replying to */
9676 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
9679 folderid = folder_item_get_identifier(compose->replyinfo->folder);
9680 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
9683 /* Message-ID of message forwarding to */
9684 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
9687 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
9688 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
9692 err |= (fprintf(fp, "X-Claws-Auto-Wrapping:%d\n", compose->autowrap) < 0);
9693 err |= (fprintf(fp, "X-Claws-Auto-Indent:%d\n", compose->autoindent) < 0);
9695 sheaders = compose_get_manual_headers_info(compose);
9696 err |= (fprintf(fp, "X-Claws-Manual-Headers:%s\n", sheaders) < 0);
9699 /* end of headers */
9700 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
9707 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
9711 if (fclose(fp) == EOF) {
9715 if (compose->targetinfo) {
9716 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
9717 flag.perm_flags = target_locked?MSG_LOCKED:0;
9719 flag.tmp_flags = MSG_DRAFT;
9721 folder_item_scan(draft);
9722 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
9723 MsgInfo *tmpinfo = NULL;
9724 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
9725 if (compose->msgid) {
9726 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
9729 msgnum = tmpinfo->msgnum;
9730 procmsg_msginfo_free(tmpinfo);
9731 debug_print("got draft msgnum %d from scanning\n", msgnum);
9733 debug_print("didn't get draft msgnum after scanning\n");
9736 debug_print("got draft msgnum %d from adding\n", msgnum);
9742 if (action != COMPOSE_AUTO_SAVE) {
9743 if (action != COMPOSE_DRAFT_FOR_EXIT)
9744 alertpanel_error(_("Could not save draft."));
9747 gtkut_window_popup(compose->window);
9748 val = alertpanel_full(_("Could not save draft"),
9749 _("Could not save draft.\n"
9750 "Do you want to cancel exit or discard this email?"),
9751 _("_Cancel exit"), _("_Discard email"), NULL,
9752 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
9753 if (val == G_ALERTALTERNATE) {
9755 g_mutex_unlock(compose->mutex); /* must be done before closing */
9756 compose_close(compose);
9760 g_mutex_unlock(compose->mutex); /* must be done before closing */
9769 if (compose->mode == COMPOSE_REEDIT) {
9770 compose_remove_reedit_target(compose, TRUE);
9773 newmsginfo = folder_item_get_msginfo(draft, msgnum);
9776 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
9778 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
9780 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
9781 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
9782 procmsg_msginfo_set_flags(newmsginfo, 0,
9783 MSG_HAS_ATTACHMENT);
9785 if (action == COMPOSE_DRAFT_FOR_EXIT) {
9786 compose_register_draft(newmsginfo);
9788 procmsg_msginfo_free(newmsginfo);
9791 folder_item_scan(draft);
9793 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
9795 g_mutex_unlock(compose->mutex); /* must be done before closing */
9796 compose_close(compose);
9802 path = folder_item_fetch_msg(draft, msgnum);
9804 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
9807 if (g_stat(path, &s) < 0) {
9808 FILE_OP_ERROR(path, "stat");
9814 procmsg_msginfo_free(compose->targetinfo);
9815 compose->targetinfo = procmsg_msginfo_new();
9816 compose->targetinfo->msgnum = msgnum;
9817 compose->targetinfo->size = (goffset)s.st_size;
9818 compose->targetinfo->mtime = s.st_mtime;
9819 compose->targetinfo->folder = draft;
9821 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
9822 compose->mode = COMPOSE_REEDIT;
9824 if (action == COMPOSE_AUTO_SAVE) {
9825 compose->autosaved_draft = compose->targetinfo;
9827 compose->modified = FALSE;
9828 compose_set_title(compose);
9832 g_mutex_unlock(compose->mutex);
9836 void compose_clear_exit_drafts(void)
9838 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9839 DRAFTED_AT_EXIT, NULL);
9840 if (is_file_exist(filepath))
9841 claws_unlink(filepath);
9846 void compose_reopen_exit_drafts(void)
9848 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9849 DRAFTED_AT_EXIT, NULL);
9850 FILE *fp = g_fopen(filepath, "rb");
9854 while (fgets(buf, sizeof(buf), fp)) {
9855 gchar **parts = g_strsplit(buf, "\t", 2);
9856 const gchar *folder = parts[0];
9857 int msgnum = parts[1] ? atoi(parts[1]):-1;
9859 if (folder && *folder && msgnum > -1) {
9860 FolderItem *item = folder_find_item_from_identifier(folder);
9861 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9863 compose_reedit(info, FALSE);
9870 compose_clear_exit_drafts();
9873 static void compose_save_cb(GtkAction *action, gpointer data)
9875 Compose *compose = (Compose *)data;
9876 compose_draft(compose, COMPOSE_KEEP_EDITING);
9877 compose->rmode = COMPOSE_REEDIT;
9880 void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9882 if (compose && file_list) {
9885 for ( tmp = file_list; tmp; tmp = tmp->next) {
9886 gchar *file = (gchar *) tmp->data;
9887 gchar *utf8_filename = conv_filename_to_utf8(file);
9888 compose_attach_append(compose, file, utf8_filename, NULL, NULL);
9889 compose_changed_cb(NULL, compose);
9894 g_free(utf8_filename);
9899 static void compose_attach_cb(GtkAction *action, gpointer data)
9901 Compose *compose = (Compose *)data;
9904 if (compose->redirect_filename != NULL)
9907 /* Set focus_window properly, in case we were called via popup menu,
9908 * which unsets it (via focus_out_event callback on compose window). */
9909 manage_window_focus_in(compose->window, NULL, NULL);
9911 file_list = filesel_select_multiple_files_open(_("Select file"));
9914 compose_attach_from_list(compose, file_list, TRUE);
9915 g_list_free(file_list);
9919 static void compose_insert_file_cb(GtkAction *action, gpointer data)
9921 Compose *compose = (Compose *)data;
9923 gint files_inserted = 0;
9925 file_list = filesel_select_multiple_files_open(_("Select file"));
9930 for ( tmp = file_list; tmp; tmp = tmp->next) {
9931 gchar *file = (gchar *) tmp->data;
9932 gchar *filedup = g_strdup(file);
9933 gchar *shortfile = g_path_get_basename(filedup);
9934 ComposeInsertResult res;
9935 /* insert the file if the file is short or if the user confirmed that
9936 he/she wants to insert the large file */
9937 res = compose_insert_file(compose, file);
9938 if (res == COMPOSE_INSERT_READ_ERROR) {
9939 alertpanel_error(_("File '%s' could not be read."), shortfile);
9940 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9941 alertpanel_error(_("File '%s' contained invalid characters\n"
9942 "for the current encoding, insertion may be incorrect."),
9944 } else if (res == COMPOSE_INSERT_SUCCESS)
9951 g_list_free(file_list);
9955 if (files_inserted > 0 && compose->gtkaspell &&
9956 compose->gtkaspell->check_while_typing)
9957 gtkaspell_highlight_all(compose->gtkaspell);
9961 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
9963 Compose *compose = (Compose *)data;
9965 compose_insert_sig(compose, FALSE);
9968 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9972 Compose *compose = (Compose *)data;
9974 gtkut_widget_get_uposition(widget, &x, &y);
9975 if (!compose->batch) {
9976 prefs_common.compose_x = x;
9977 prefs_common.compose_y = y;
9979 if (compose->sending || compose->updating)
9981 compose_close_cb(NULL, compose);
9985 void compose_close_toolbar(Compose *compose)
9987 compose_close_cb(NULL, compose);
9990 static void compose_close_cb(GtkAction *action, gpointer data)
9992 Compose *compose = (Compose *)data;
9996 if (compose->exteditor_tag != -1) {
9997 if (!compose_ext_editor_kill(compose))
10002 if (compose->modified) {
10003 gboolean reedit = (compose->rmode == COMPOSE_REEDIT);
10004 if (!g_mutex_trylock(compose->mutex)) {
10005 /* we don't want to lock the mutex once it's available,
10006 * because as the only other part of compose.c locking
10007 * it is compose_close - which means once unlocked,
10008 * the compose struct will be freed */
10009 debug_print("couldn't lock mutex, probably sending\n");
10013 val = alertpanel(_("Discard message"),
10014 _("This message has been modified. Discard it?"),
10015 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
10017 val = alertpanel(_("Save changes"),
10018 _("This message has been modified. Save the latest changes?"),
10019 _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
10021 g_mutex_unlock(compose->mutex);
10023 case G_ALERTDEFAULT:
10024 if (prefs_common.autosave && !reedit)
10025 compose_remove_draft(compose);
10027 case G_ALERTALTERNATE:
10028 compose_draft(data, COMPOSE_QUIT_EDITING);
10035 compose_close(compose);
10038 static void compose_print_cb(GtkAction *action, gpointer data)
10040 Compose *compose = (Compose *) data;
10042 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
10043 if (compose->targetinfo)
10044 messageview_print(compose->targetinfo, FALSE, -1, -1, 0);
10047 static void compose_set_encoding_cb(GtkAction *action, GtkRadioAction *current, gpointer data)
10049 gboolean active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
10050 gint value = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
10051 Compose *compose = (Compose *) data;
10054 compose->out_encoding = (CharSet)value;
10057 static void compose_address_cb(GtkAction *action, gpointer data)
10059 Compose *compose = (Compose *)data;
10061 #ifndef USE_NEW_ADDRBOOK
10062 addressbook_open(compose);
10064 GError* error = NULL;
10065 addressbook_connect_signals(compose);
10066 addressbook_dbus_open(TRUE, &error);
10068 g_warning("%s", error->message);
10069 g_error_free(error);
10074 static void about_show_cb(GtkAction *action, gpointer data)
10079 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
10081 Compose *compose = (Compose *)data;
10086 tmpl = g_object_get_data(G_OBJECT(widget), "template");
10087 cm_return_if_fail(tmpl != NULL);
10089 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
10091 val = alertpanel(_("Apply template"), msg,
10092 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
10095 if (val == G_ALERTDEFAULT)
10096 compose_template_apply(compose, tmpl, TRUE);
10097 else if (val == G_ALERTALTERNATE)
10098 compose_template_apply(compose, tmpl, FALSE);
10101 static void compose_ext_editor_cb(GtkAction *action, gpointer data)
10103 Compose *compose = (Compose *)data;
10105 compose_exec_ext_editor(compose);
10108 static void compose_undo_cb(GtkAction *action, gpointer data)
10110 Compose *compose = (Compose *)data;
10111 gboolean prev_autowrap = compose->autowrap;
10113 compose->autowrap = FALSE;
10114 undo_undo(compose->undostruct);
10115 compose->autowrap = prev_autowrap;
10118 static void compose_redo_cb(GtkAction *action, gpointer data)
10120 Compose *compose = (Compose *)data;
10121 gboolean prev_autowrap = compose->autowrap;
10123 compose->autowrap = FALSE;
10124 undo_redo(compose->undostruct);
10125 compose->autowrap = prev_autowrap;
10128 static void entry_cut_clipboard(GtkWidget *entry)
10130 if (GTK_IS_EDITABLE(entry))
10131 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
10132 else if (GTK_IS_TEXT_VIEW(entry))
10133 gtk_text_buffer_cut_clipboard(
10134 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10135 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
10139 static void entry_copy_clipboard(GtkWidget *entry)
10141 if (GTK_IS_EDITABLE(entry))
10142 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
10143 else if (GTK_IS_TEXT_VIEW(entry))
10144 gtk_text_buffer_copy_clipboard(
10145 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
10146 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
10149 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
10150 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
10152 if (GTK_IS_TEXT_VIEW(entry)) {
10153 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10154 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
10155 GtkTextIter start_iter, end_iter;
10157 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
10159 if (contents == NULL)
10162 /* we shouldn't delete the selection when middle-click-pasting, or we
10163 * can't mid-click-paste our own selection */
10164 if (clip != GDK_SELECTION_PRIMARY) {
10165 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
10166 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
10169 if (insert_place == NULL) {
10170 /* if insert_place isn't specified, insert at the cursor.
10171 * used for Ctrl-V pasting */
10172 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10173 start = gtk_text_iter_get_offset(&start_iter);
10174 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
10176 /* if insert_place is specified, paste here.
10177 * used for mid-click-pasting */
10178 start = gtk_text_iter_get_offset(insert_place);
10179 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
10180 if (prefs_common.primary_paste_unselects)
10181 gtk_text_buffer_select_range(buffer, insert_place, insert_place);
10185 /* paste unwrapped: mark the paste so it's not wrapped later */
10186 end = start + strlen(contents);
10187 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
10188 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
10189 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
10190 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
10191 /* rewrap paragraph now (after a mid-click-paste) */
10192 mark_start = gtk_text_buffer_get_insert(buffer);
10193 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
10194 gtk_text_iter_backward_char(&start_iter);
10195 compose_beautify_paragraph(compose, &start_iter, TRUE);
10197 } else if (GTK_IS_EDITABLE(entry))
10198 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
10200 compose->modified = TRUE;
10203 static void entry_allsel(GtkWidget *entry)
10205 if (GTK_IS_EDITABLE(entry))
10206 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
10207 else if (GTK_IS_TEXT_VIEW(entry)) {
10208 GtkTextIter startiter, enditer;
10209 GtkTextBuffer *textbuf;
10211 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
10212 gtk_text_buffer_get_start_iter(textbuf, &startiter);
10213 gtk_text_buffer_get_end_iter(textbuf, &enditer);
10215 gtk_text_buffer_move_mark_by_name(textbuf,
10216 "selection_bound", &startiter);
10217 gtk_text_buffer_move_mark_by_name(textbuf,
10218 "insert", &enditer);
10222 static void compose_cut_cb(GtkAction *action, gpointer data)
10224 Compose *compose = (Compose *)data;
10225 if (compose->focused_editable
10226 #ifndef GENERIC_UMPC
10227 && gtk_widget_has_focus(compose->focused_editable)
10230 entry_cut_clipboard(compose->focused_editable);
10233 static void compose_copy_cb(GtkAction *action, gpointer data)
10235 Compose *compose = (Compose *)data;
10236 if (compose->focused_editable
10237 #ifndef GENERIC_UMPC
10238 && gtk_widget_has_focus(compose->focused_editable)
10241 entry_copy_clipboard(compose->focused_editable);
10244 static void compose_paste_cb(GtkAction *action, gpointer data)
10246 Compose *compose = (Compose *)data;
10247 gint prev_autowrap;
10248 GtkTextBuffer *buffer;
10250 if (compose->focused_editable &&
10251 #ifndef GENERIC_UMPC
10252 gtk_widget_has_focus(compose->focused_editable)
10255 entry_paste_clipboard(compose, compose->focused_editable,
10256 prefs_common.linewrap_pastes,
10257 GDK_SELECTION_CLIPBOARD, NULL);
10262 #ifndef GENERIC_UMPC
10263 gtk_widget_has_focus(compose->text) &&
10265 compose->gtkaspell &&
10266 compose->gtkaspell->check_while_typing)
10267 gtkaspell_highlight_all(compose->gtkaspell);
10271 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
10273 Compose *compose = (Compose *)data;
10274 gint wrap_quote = prefs_common.linewrap_quote;
10275 if (compose->focused_editable
10276 #ifndef GENERIC_UMPC
10277 && gtk_widget_has_focus(compose->focused_editable)
10280 /* let text_insert() (called directly or at a later time
10281 * after the gtk_editable_paste_clipboard) know that
10282 * text is to be inserted as a quotation. implemented
10283 * by using a simple refcount... */
10284 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
10285 G_OBJECT(compose->focused_editable),
10286 "paste_as_quotation"));
10287 g_object_set_data(G_OBJECT(compose->focused_editable),
10288 "paste_as_quotation",
10289 GINT_TO_POINTER(paste_as_quotation + 1));
10290 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
10291 entry_paste_clipboard(compose, compose->focused_editable,
10292 prefs_common.linewrap_pastes,
10293 GDK_SELECTION_CLIPBOARD, NULL);
10294 prefs_common.linewrap_quote = wrap_quote;
10298 static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
10300 Compose *compose = (Compose *)data;
10301 gint prev_autowrap;
10302 GtkTextBuffer *buffer;
10304 if (compose->focused_editable
10305 #ifndef GENERIC_UMPC
10306 && gtk_widget_has_focus(compose->focused_editable)
10309 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
10310 GDK_SELECTION_CLIPBOARD, NULL);
10315 #ifndef GENERIC_UMPC
10316 gtk_widget_has_focus(compose->text) &&
10318 compose->gtkaspell &&
10319 compose->gtkaspell->check_while_typing)
10320 gtkaspell_highlight_all(compose->gtkaspell);
10324 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
10326 Compose *compose = (Compose *)data;
10327 gint prev_autowrap;
10328 GtkTextBuffer *buffer;
10330 if (compose->focused_editable
10331 #ifndef GENERIC_UMPC
10332 && gtk_widget_has_focus(compose->focused_editable)
10335 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
10336 GDK_SELECTION_CLIPBOARD, NULL);
10341 #ifndef GENERIC_UMPC
10342 gtk_widget_has_focus(compose->text) &&
10344 compose->gtkaspell &&
10345 compose->gtkaspell->check_while_typing)
10346 gtkaspell_highlight_all(compose->gtkaspell);
10350 static void compose_allsel_cb(GtkAction *action, gpointer data)
10352 Compose *compose = (Compose *)data;
10353 if (compose->focused_editable
10354 #ifndef GENERIC_UMPC
10355 && gtk_widget_has_focus(compose->focused_editable)
10358 entry_allsel(compose->focused_editable);
10361 static void textview_move_beginning_of_line (GtkTextView *text)
10363 GtkTextBuffer *buffer;
10367 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10369 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10370 mark = gtk_text_buffer_get_insert(buffer);
10371 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10372 gtk_text_iter_set_line_offset(&ins, 0);
10373 gtk_text_buffer_place_cursor(buffer, &ins);
10376 static void textview_move_forward_character (GtkTextView *text)
10378 GtkTextBuffer *buffer;
10382 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10384 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10385 mark = gtk_text_buffer_get_insert(buffer);
10386 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10387 if (gtk_text_iter_forward_cursor_position(&ins))
10388 gtk_text_buffer_place_cursor(buffer, &ins);
10391 static void textview_move_backward_character (GtkTextView *text)
10393 GtkTextBuffer *buffer;
10397 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10399 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10400 mark = gtk_text_buffer_get_insert(buffer);
10401 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10402 if (gtk_text_iter_backward_cursor_position(&ins))
10403 gtk_text_buffer_place_cursor(buffer, &ins);
10406 static void textview_move_forward_word (GtkTextView *text)
10408 GtkTextBuffer *buffer;
10413 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10415 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10416 mark = gtk_text_buffer_get_insert(buffer);
10417 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10418 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
10419 if (gtk_text_iter_forward_word_ends(&ins, count)) {
10420 gtk_text_iter_backward_word_start(&ins);
10421 gtk_text_buffer_place_cursor(buffer, &ins);
10425 static void textview_move_backward_word (GtkTextView *text)
10427 GtkTextBuffer *buffer;
10431 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10433 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10434 mark = gtk_text_buffer_get_insert(buffer);
10435 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10436 if (gtk_text_iter_backward_word_starts(&ins, 1))
10437 gtk_text_buffer_place_cursor(buffer, &ins);
10440 static void textview_move_end_of_line (GtkTextView *text)
10442 GtkTextBuffer *buffer;
10446 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10448 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10449 mark = gtk_text_buffer_get_insert(buffer);
10450 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10451 if (gtk_text_iter_forward_to_line_end(&ins))
10452 gtk_text_buffer_place_cursor(buffer, &ins);
10455 static void textview_move_next_line (GtkTextView *text)
10457 GtkTextBuffer *buffer;
10462 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10464 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10465 mark = gtk_text_buffer_get_insert(buffer);
10466 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10467 offset = gtk_text_iter_get_line_offset(&ins);
10468 if (gtk_text_iter_forward_line(&ins)) {
10469 gtk_text_iter_set_line_offset(&ins, offset);
10470 gtk_text_buffer_place_cursor(buffer, &ins);
10474 static void textview_move_previous_line (GtkTextView *text)
10476 GtkTextBuffer *buffer;
10481 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10483 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10484 mark = gtk_text_buffer_get_insert(buffer);
10485 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10486 offset = gtk_text_iter_get_line_offset(&ins);
10487 if (gtk_text_iter_backward_line(&ins)) {
10488 gtk_text_iter_set_line_offset(&ins, offset);
10489 gtk_text_buffer_place_cursor(buffer, &ins);
10493 static void textview_delete_forward_character (GtkTextView *text)
10495 GtkTextBuffer *buffer;
10497 GtkTextIter ins, end_iter;
10499 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10501 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10502 mark = gtk_text_buffer_get_insert(buffer);
10503 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10505 if (gtk_text_iter_forward_char(&end_iter)) {
10506 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10510 static void textview_delete_backward_character (GtkTextView *text)
10512 GtkTextBuffer *buffer;
10514 GtkTextIter ins, end_iter;
10516 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10518 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10519 mark = gtk_text_buffer_get_insert(buffer);
10520 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10522 if (gtk_text_iter_backward_char(&end_iter)) {
10523 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10527 static void textview_delete_forward_word (GtkTextView *text)
10529 GtkTextBuffer *buffer;
10531 GtkTextIter ins, end_iter;
10533 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10535 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10536 mark = gtk_text_buffer_get_insert(buffer);
10537 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10539 if (gtk_text_iter_forward_word_end(&end_iter)) {
10540 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10544 static void textview_delete_backward_word (GtkTextView *text)
10546 GtkTextBuffer *buffer;
10548 GtkTextIter ins, end_iter;
10550 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10552 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10553 mark = gtk_text_buffer_get_insert(buffer);
10554 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10556 if (gtk_text_iter_backward_word_start(&end_iter)) {
10557 gtk_text_buffer_delete(buffer, &end_iter, &ins);
10561 static void textview_delete_line (GtkTextView *text)
10563 GtkTextBuffer *buffer;
10565 GtkTextIter ins, start_iter, end_iter;
10567 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10569 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10570 mark = gtk_text_buffer_get_insert(buffer);
10571 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10574 gtk_text_iter_set_line_offset(&start_iter, 0);
10577 if (gtk_text_iter_ends_line(&end_iter)){
10578 if (!gtk_text_iter_forward_char(&end_iter))
10579 gtk_text_iter_backward_char(&start_iter);
10582 gtk_text_iter_forward_to_line_end(&end_iter);
10583 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
10586 static void textview_delete_to_line_end (GtkTextView *text)
10588 GtkTextBuffer *buffer;
10590 GtkTextIter ins, end_iter;
10592 cm_return_if_fail(GTK_IS_TEXT_VIEW(text));
10594 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
10595 mark = gtk_text_buffer_get_insert(buffer);
10596 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
10598 if (gtk_text_iter_ends_line(&end_iter))
10599 gtk_text_iter_forward_char(&end_iter);
10601 gtk_text_iter_forward_to_line_end(&end_iter);
10602 gtk_text_buffer_delete(buffer, &ins, &end_iter);
10605 #define DO_ACTION(name, act) { \
10606 if(!strcmp(name, a_name)) { \
10610 static ComposeCallAdvancedAction compose_call_advanced_action_from_path(GtkAction *action)
10612 const gchar *a_name = gtk_action_get_name(action);
10613 DO_ACTION("Edit/Advanced/BackChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER);
10614 DO_ACTION("Edit/Advanced/ForwChar", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER);
10615 DO_ACTION("Edit/Advanced/BackWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD);
10616 DO_ACTION("Edit/Advanced/ForwWord", COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD);
10617 DO_ACTION("Edit/Advanced/BegLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE);
10618 DO_ACTION("Edit/Advanced/EndLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE);
10619 DO_ACTION("Edit/Advanced/PrevLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE);
10620 DO_ACTION("Edit/Advanced/NextLine", COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE);
10621 DO_ACTION("Edit/Advanced/DelBackChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER);
10622 DO_ACTION("Edit/Advanced/DelForwChar", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER);
10623 DO_ACTION("Edit/Advanced/DelBackWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD);
10624 DO_ACTION("Edit/Advanced/DelForwWord", COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD);
10625 DO_ACTION("Edit/Advanced/DelLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE);
10626 DO_ACTION("Edit/Advanced/DelEndLine", COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END);
10630 static void compose_advanced_action_cb(GtkAction *gaction, gpointer data)
10632 Compose *compose = (Compose *)data;
10633 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10634 ComposeCallAdvancedAction action = -1;
10636 action = compose_call_advanced_action_from_path(gaction);
10639 void (*do_action) (GtkTextView *text);
10640 } action_table[] = {
10641 {textview_move_beginning_of_line},
10642 {textview_move_forward_character},
10643 {textview_move_backward_character},
10644 {textview_move_forward_word},
10645 {textview_move_backward_word},
10646 {textview_move_end_of_line},
10647 {textview_move_next_line},
10648 {textview_move_previous_line},
10649 {textview_delete_forward_character},
10650 {textview_delete_backward_character},
10651 {textview_delete_forward_word},
10652 {textview_delete_backward_word},
10653 {textview_delete_line},
10654 {textview_delete_to_line_end}
10657 if (!gtk_widget_has_focus(GTK_WIDGET(text))) return;
10659 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
10660 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
10661 if (action_table[action].do_action)
10662 action_table[action].do_action(text);
10664 g_warning("Not implemented yet.");
10668 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
10670 GtkAllocation allocation;
10674 if (GTK_IS_EDITABLE(widget)) {
10675 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
10676 gtk_editable_set_position(GTK_EDITABLE(widget),
10679 if ((parent = gtk_widget_get_parent(widget))
10680 && (parent = gtk_widget_get_parent(parent))
10681 && (parent = gtk_widget_get_parent(parent))) {
10682 if (GTK_IS_SCROLLED_WINDOW(parent)) {
10683 gtk_widget_get_allocation(widget, &allocation);
10684 gint y = allocation.y;
10685 gint height = allocation.height;
10686 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
10687 (GTK_SCROLLED_WINDOW(parent));
10689 gfloat value = gtk_adjustment_get_value(shown);
10690 gfloat upper = gtk_adjustment_get_upper(shown);
10691 gfloat page_size = gtk_adjustment_get_page_size(shown);
10692 if (y < (int)value) {
10693 gtk_adjustment_set_value(shown, y - 1);
10695 if ((y + height) > ((int)value + (int)page_size)) {
10696 if ((y - height - 1) < ((int)upper - (int)page_size)) {
10697 gtk_adjustment_set_value(shown,
10698 y + height - (int)page_size - 1);
10700 gtk_adjustment_set_value(shown,
10701 (int)upper - (int)page_size - 1);
10708 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
10709 compose->focused_editable = widget;
10711 #ifdef GENERIC_UMPC
10712 if (GTK_IS_TEXT_VIEW(widget)
10713 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
10714 g_object_ref(compose->notebook);
10715 g_object_ref(compose->edit_vbox);
10716 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10717 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10718 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
10719 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
10720 g_object_unref(compose->notebook);
10721 g_object_unref(compose->edit_vbox);
10722 g_signal_handlers_block_by_func(G_OBJECT(widget),
10723 G_CALLBACK(compose_grab_focus_cb),
10725 gtk_widget_grab_focus(widget);
10726 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10727 G_CALLBACK(compose_grab_focus_cb),
10729 } else if (!GTK_IS_TEXT_VIEW(widget)
10730 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
10731 g_object_ref(compose->notebook);
10732 g_object_ref(compose->edit_vbox);
10733 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
10734 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
10735 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
10736 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
10737 g_object_unref(compose->notebook);
10738 g_object_unref(compose->edit_vbox);
10739 g_signal_handlers_block_by_func(G_OBJECT(widget),
10740 G_CALLBACK(compose_grab_focus_cb),
10742 gtk_widget_grab_focus(widget);
10743 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
10744 G_CALLBACK(compose_grab_focus_cb),
10750 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
10752 compose->modified = TRUE;
10753 // compose_beautify_paragraph(compose, NULL, TRUE);
10754 #ifndef GENERIC_UMPC
10755 compose_set_title(compose);
10759 static void compose_wrap_cb(GtkAction *action, gpointer data)
10761 Compose *compose = (Compose *)data;
10762 compose_beautify_paragraph(compose, NULL, TRUE);
10765 static void compose_wrap_all_cb(GtkAction *action, gpointer data)
10767 Compose *compose = (Compose *)data;
10768 compose_wrap_all_full(compose, TRUE);
10771 static void compose_find_cb(GtkAction *action, gpointer data)
10773 Compose *compose = (Compose *)data;
10775 message_search_compose(compose);
10778 static void compose_toggle_autowrap_cb(GtkToggleAction *action,
10781 Compose *compose = (Compose *)data;
10782 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10783 if (compose->autowrap)
10784 compose_wrap_all_full(compose, TRUE);
10785 compose->autowrap = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10788 static void compose_toggle_autoindent_cb(GtkToggleAction *action,
10791 Compose *compose = (Compose *)data;
10792 compose->autoindent = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10795 static void compose_toggle_sign_cb(GtkToggleAction *action, gpointer data)
10797 Compose *compose = (Compose *)data;
10799 compose->use_signing = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10802 static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
10804 Compose *compose = (Compose *)data;
10806 compose->use_encryption = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
10809 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
10811 g_free(compose->privacy_system);
10813 compose->privacy_system = g_strdup(account->default_privacy_system);
10814 compose_update_privacy_system_menu_item(compose, warn);
10817 static void compose_toggle_ruler_cb(GtkToggleAction *action, gpointer data)
10819 Compose *compose = (Compose *)data;
10821 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
10822 gtk_widget_show(compose->ruler_hbox);
10823 prefs_common.show_ruler = TRUE;
10825 gtk_widget_hide(compose->ruler_hbox);
10826 gtk_widget_queue_resize(compose->edit_vbox);
10827 prefs_common.show_ruler = FALSE;
10831 static void compose_attach_drag_received_cb (GtkWidget *widget,
10832 GdkDragContext *context,
10835 GtkSelectionData *data,
10838 gpointer user_data)
10840 Compose *compose = (Compose *)user_data;
10844 type = gtk_selection_data_get_data_type(data);
10845 if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
10847 || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
10849 ) && gtk_drag_get_source_widget(context) !=
10850 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10851 list = uri_list_extract_filenames(
10852 (const gchar *)gtk_selection_data_get_data(data));
10853 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10854 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
10855 compose_attach_append
10856 (compose, (const gchar *)tmp->data,
10857 utf8_filename, NULL, NULL);
10858 g_free(utf8_filename);
10860 if (list) compose_changed_cb(NULL, compose);
10861 list_free_strings(list);
10863 } else if (gtk_drag_get_source_widget(context)
10864 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
10865 /* comes from our summaryview */
10866 SummaryView * summaryview = NULL;
10867 GSList * list = NULL, *cur = NULL;
10869 if (mainwindow_get_mainwindow())
10870 summaryview = mainwindow_get_mainwindow()->summaryview;
10873 list = summary_get_selected_msg_list(summaryview);
10875 for (cur = list; cur; cur = cur->next) {
10876 MsgInfo *msginfo = (MsgInfo *)cur->data;
10877 gchar *file = NULL;
10879 file = procmsg_get_message_file_full(msginfo,
10882 compose_attach_append(compose, (const gchar *)file,
10883 (const gchar *)file, "message/rfc822", NULL);
10887 g_slist_free(list);
10891 static gboolean compose_drag_drop(GtkWidget *widget,
10892 GdkDragContext *drag_context,
10894 guint time, gpointer user_data)
10896 /* not handling this signal makes compose_insert_drag_received_cb
10901 static gboolean completion_set_focus_to_subject
10902 (GtkWidget *widget,
10903 GdkEventKey *event,
10906 cm_return_val_if_fail(compose != NULL, FALSE);
10908 /* make backtab move to subject field */
10909 if(event->keyval == GDK_KEY_ISO_Left_Tab) {
10910 gtk_widget_grab_focus(compose->subject_entry);
10916 static void compose_insert_drag_received_cb (GtkWidget *widget,
10917 GdkDragContext *drag_context,
10920 GtkSelectionData *data,
10923 gpointer user_data)
10925 Compose *compose = (Compose *)user_data;
10929 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
10931 type = gtk_selection_data_get_data_type(data);
10933 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
10935 if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
10937 AlertValue val = G_ALERTDEFAULT;
10938 const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
10940 list = uri_list_extract_filenames(ddata);
10941 if (list == NULL && strstr(ddata, "://")) {
10942 /* Assume a list of no files, and data has ://, is a remote link */
10943 gchar *tmpdata = g_strstrip(g_strdup(ddata));
10944 gchar *tmpfile = get_tmp_file();
10945 str_write_to_file(tmpdata, tmpfile);
10947 compose_insert_file(compose, tmpfile);
10948 claws_unlink(tmpfile);
10950 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10951 compose_beautify_paragraph(compose, NULL, TRUE);
10954 switch (prefs_common.compose_dnd_mode) {
10955 case COMPOSE_DND_ASK:
10956 val = alertpanel_full(_("Insert or attach?"),
10957 _("Do you want to insert the contents of the file(s) "
10958 "into the message body, or attach it to the email?"),
10959 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
10960 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
10962 case COMPOSE_DND_INSERT:
10963 val = G_ALERTALTERNATE;
10965 case COMPOSE_DND_ATTACH:
10966 val = G_ALERTOTHER;
10969 /* unexpected case */
10970 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
10973 if (val & G_ALERTDISABLE) {
10974 val &= ~G_ALERTDISABLE;
10975 /* remember what action to perform by default, only if we don't click Cancel */
10976 if (val == G_ALERTALTERNATE)
10977 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
10978 else if (val == G_ALERTOTHER)
10979 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
10982 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
10983 gtk_drag_finish(drag_context, FALSE, FALSE, time);
10984 list_free_strings(list);
10987 } else if (val == G_ALERTOTHER) {
10988 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
10989 list_free_strings(list);
10994 for (tmp = list; tmp != NULL; tmp = tmp->next) {
10995 compose_insert_file(compose, (const gchar *)tmp->data);
10997 list_free_strings(list);
10999 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11004 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11007 static void compose_header_drag_received_cb (GtkWidget *widget,
11008 GdkDragContext *drag_context,
11011 GtkSelectionData *data,
11014 gpointer user_data)
11016 GtkEditable *entry = (GtkEditable *)user_data;
11017 const gchar *email = (const gchar *)gtk_selection_data_get_data(data);
11019 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
11022 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
11023 gchar *decoded=g_new(gchar, strlen(email));
11026 decode_uri(decoded, email + strlen("mailto:")); /* will fit */
11027 gtk_editable_delete_text(entry, 0, -1);
11028 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
11029 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11033 gtk_drag_finish(drag_context, TRUE, FALSE, time);
11036 static void compose_toggle_return_receipt_cb(GtkToggleAction *action, gpointer data)
11038 Compose *compose = (Compose *)data;
11040 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11041 compose->return_receipt = TRUE;
11043 compose->return_receipt = FALSE;
11046 static void compose_toggle_remove_refs_cb(GtkToggleAction *action, gpointer data)
11048 Compose *compose = (Compose *)data;
11050 if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
11051 compose->remove_references = TRUE;
11053 compose->remove_references = FALSE;
11056 static gboolean compose_headerentry_button_clicked_cb (GtkWidget *button,
11057 ComposeHeaderEntry *headerentry)
11059 gtk_entry_set_text(GTK_ENTRY(headerentry->entry), "");
11063 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
11064 GdkEventKey *event,
11065 ComposeHeaderEntry *headerentry)
11067 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
11068 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
11069 !(event->state & GDK_MODIFIER_MASK) &&
11070 (event->keyval == GDK_KEY_BackSpace) &&
11071 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
11072 gtk_container_remove
11073 (GTK_CONTAINER(headerentry->compose->header_table),
11074 headerentry->combo);
11075 gtk_container_remove
11076 (GTK_CONTAINER(headerentry->compose->header_table),
11077 headerentry->entry);
11078 headerentry->compose->header_list =
11079 g_slist_remove(headerentry->compose->header_list,
11081 g_free(headerentry);
11082 } else if (event->keyval == GDK_KEY_Tab) {
11083 if (headerentry->compose->header_last == headerentry) {
11084 /* Override default next focus, and give it to subject_entry
11085 * instead of notebook tabs
11087 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
11088 gtk_widget_grab_focus(headerentry->compose->subject_entry);
11095 static gboolean scroll_postpone(gpointer data)
11097 Compose *compose = (Compose *)data;
11099 cm_return_val_if_fail(!compose->batch, FALSE);
11101 GTK_EVENTS_FLUSH();
11102 compose_show_first_last_header(compose, FALSE);
11106 static void compose_headerentry_changed_cb(GtkWidget *entry,
11107 ComposeHeaderEntry *headerentry)
11109 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
11110 compose_create_header_entry(headerentry->compose);
11111 g_signal_handlers_disconnect_matched
11112 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
11113 0, 0, NULL, NULL, headerentry);
11115 if (!headerentry->compose->batch)
11116 g_timeout_add(0, scroll_postpone, headerentry->compose);
11120 static gboolean compose_defer_auto_save_draft(Compose *compose)
11122 compose->draft_timeout_tag = -1;
11123 compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
11127 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
11129 GtkAdjustment *vadj;
11131 cm_return_if_fail(compose);
11132 cm_return_if_fail(!compose->batch);
11133 cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
11134 cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
11135 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
11136 gtk_widget_get_parent(compose->header_table)));
11137 gtk_adjustment_set_value(vadj, (show_first ?
11138 gtk_adjustment_get_lower(vadj) :
11139 (gtk_adjustment_get_upper(vadj) -
11140 gtk_adjustment_get_page_size(vadj))));
11141 gtk_adjustment_changed(vadj);
11144 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
11145 const gchar *text, gint len, Compose *compose)
11147 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
11148 (G_OBJECT(compose->text), "paste_as_quotation"));
11151 cm_return_if_fail(text != NULL);
11153 g_signal_handlers_block_by_func(G_OBJECT(buffer),
11154 G_CALLBACK(text_inserted),
11156 if (paste_as_quotation) {
11158 const gchar *qmark;
11160 GtkTextIter start_iter;
11163 len = strlen(text);
11165 new_text = g_strndup(text, len);
11167 qmark = compose_quote_char_from_context(compose);
11169 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11170 gtk_text_buffer_place_cursor(buffer, iter);
11172 pos = gtk_text_iter_get_offset(iter);
11174 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
11175 _("Quote format error at line %d."));
11176 quote_fmt_reset_vartable();
11178 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
11179 GINT_TO_POINTER(paste_as_quotation - 1));
11181 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11182 gtk_text_buffer_place_cursor(buffer, iter);
11183 gtk_text_buffer_delete_mark(buffer, mark);
11185 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
11186 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
11187 compose_beautify_paragraph(compose, &start_iter, FALSE);
11188 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
11189 gtk_text_buffer_delete_mark(buffer, mark);
11191 if (strcmp(text, "\n") || compose->automatic_break
11192 || gtk_text_iter_starts_line(iter)) {
11193 GtkTextIter before_ins;
11194 gtk_text_buffer_insert(buffer, iter, text, len);
11195 if (!strstr(text, "\n") && gtk_text_iter_has_tag(iter, compose->no_join_tag)) {
11196 before_ins = *iter;
11197 gtk_text_iter_backward_chars(&before_ins, len);
11198 gtk_text_buffer_remove_tag_by_name(buffer, "no_join", &before_ins, iter);
11201 /* check if the preceding is just whitespace or quote */
11202 GtkTextIter start_line;
11203 gchar *tmp = NULL, *quote = NULL;
11204 gint quote_len = 0, is_normal = 0;
11205 start_line = *iter;
11206 gtk_text_iter_set_line_offset(&start_line, 0);
11207 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
11210 if (*tmp == '\0') {
11213 quote = compose_get_quote_str(buffer, &start_line, "e_len);
11221 gtk_text_buffer_insert(buffer, iter, text, len);
11223 gtk_text_buffer_insert_with_tags_by_name(buffer,
11224 iter, text, len, "no_join", NULL);
11229 if (!paste_as_quotation) {
11230 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
11231 compose_beautify_paragraph(compose, iter, FALSE);
11232 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
11233 gtk_text_buffer_delete_mark(buffer, mark);
11236 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
11237 G_CALLBACK(text_inserted),
11239 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
11241 if (prefs_common.autosave &&
11242 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
11243 compose->draft_timeout_tag != -2 /* disabled while loading */)
11244 compose->draft_timeout_tag = g_timeout_add
11245 (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
11249 static void compose_check_all(GtkAction *action, gpointer data)
11251 Compose *compose = (Compose *)data;
11252 if (!compose->gtkaspell)
11255 if (gtk_widget_has_focus(compose->subject_entry))
11256 claws_spell_entry_check_all(
11257 CLAWS_SPELL_ENTRY(compose->subject_entry));
11259 gtkaspell_check_all(compose->gtkaspell);
11262 static void compose_highlight_all(GtkAction *action, gpointer data)
11264 Compose *compose = (Compose *)data;
11265 if (compose->gtkaspell) {
11266 claws_spell_entry_recheck_all(
11267 CLAWS_SPELL_ENTRY(compose->subject_entry));
11268 gtkaspell_highlight_all(compose->gtkaspell);
11272 static void compose_check_backwards(GtkAction *action, gpointer data)
11274 Compose *compose = (Compose *)data;
11275 if (!compose->gtkaspell) {
11276 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11280 if (gtk_widget_has_focus(compose->subject_entry))
11281 claws_spell_entry_check_backwards(
11282 CLAWS_SPELL_ENTRY(compose->subject_entry));
11284 gtkaspell_check_backwards(compose->gtkaspell);
11287 static void compose_check_forwards_go(GtkAction *action, gpointer data)
11289 Compose *compose = (Compose *)data;
11290 if (!compose->gtkaspell) {
11291 cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
11295 if (gtk_widget_has_focus(compose->subject_entry))
11296 claws_spell_entry_check_forwards_go(
11297 CLAWS_SPELL_ENTRY(compose->subject_entry));
11299 gtkaspell_check_forwards_go(compose->gtkaspell);
11304 *\brief Guess originating forward account from MsgInfo and several
11305 * "common preference" settings. Return NULL if no guess.
11307 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
11309 PrefsAccount *account = NULL;
11311 cm_return_val_if_fail(msginfo, NULL);
11312 cm_return_val_if_fail(msginfo->folder, NULL);
11313 cm_return_val_if_fail(msginfo->folder->prefs, NULL);
11315 if (msginfo->folder->prefs->enable_default_account)
11316 account = account_find_from_id(msginfo->folder->prefs->default_account);
11319 account = msginfo->folder->folder->account;
11321 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
11323 Xstrdup_a(to, msginfo->to, return NULL);
11324 extract_address(to);
11325 account = account_find_from_address(to, FALSE);
11328 if (!account && prefs_common.forward_account_autosel) {
11329 gchar cc[BUFFSIZE];
11330 if (!procheader_get_header_from_msginfo
11331 (msginfo, cc,sizeof cc , "Cc:")) {
11332 gchar *buf = cc + strlen("Cc:");
11333 extract_address(buf);
11334 account = account_find_from_address(buf, FALSE);
11338 if (!account && prefs_common.forward_account_autosel) {
11339 gchar deliveredto[BUFFSIZE];
11340 if (!procheader_get_header_from_msginfo
11341 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
11342 gchar *buf = deliveredto + strlen("Delivered-To:");
11343 extract_address(buf);
11344 account = account_find_from_address(buf, FALSE);
11351 gboolean compose_close(Compose *compose)
11355 if (!g_mutex_trylock(compose->mutex)) {
11356 /* we have to wait for the (possibly deferred by auto-save)
11357 * drafting to be done, before destroying the compose under
11359 debug_print("waiting for drafting to finish...\n");
11360 compose_allow_user_actions(compose, FALSE);
11361 g_timeout_add (500, (GSourceFunc) compose_close, compose);
11364 cm_return_val_if_fail(compose, FALSE);
11365 gtkut_widget_get_uposition(compose->window, &x, &y);
11366 if (!compose->batch) {
11367 prefs_common.compose_x = x;
11368 prefs_common.compose_y = y;
11370 g_mutex_unlock(compose->mutex);
11371 compose_destroy(compose);
11376 * Add entry field for each address in list.
11377 * \param compose E-Mail composition object.
11378 * \param listAddress List of (formatted) E-Mail addresses.
11380 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
11383 node = listAddress;
11385 addr = ( gchar * ) node->data;
11386 compose_entry_append( compose, addr, COMPOSE_TO, PREF_NONE );
11387 node = g_list_next( node );
11391 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
11392 guint action, gboolean opening_multiple)
11394 gchar *body = NULL;
11395 GSList *new_msglist = NULL;
11396 MsgInfo *tmp_msginfo = NULL;
11397 gboolean originally_enc = FALSE;
11398 gboolean originally_sig = FALSE;
11399 Compose *compose = NULL;
11400 gchar *s_system = NULL;
11402 cm_return_if_fail(msgview != NULL);
11404 cm_return_if_fail(msginfo_list != NULL);
11406 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
11407 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
11408 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
11410 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
11411 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
11412 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
11413 orig_msginfo, mimeinfo);
11414 if (tmp_msginfo != NULL) {
11415 new_msglist = g_slist_append(NULL, tmp_msginfo);
11417 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
11418 privacy_msginfo_get_signed_state(orig_msginfo, &s_system);
11419 originally_sig = MSG_IS_SIGNED(orig_msginfo->flags);
11421 tmp_msginfo->folder = orig_msginfo->folder;
11422 tmp_msginfo->msgnum = orig_msginfo->msgnum;
11423 if (orig_msginfo->tags) {
11424 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
11425 tmp_msginfo->folder->tags_dirty = TRUE;
11431 if (!opening_multiple)
11432 body = messageview_get_selection(msgview);
11435 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
11436 procmsg_msginfo_free(tmp_msginfo);
11437 g_slist_free(new_msglist);
11439 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
11441 if (compose && originally_enc) {
11442 compose_force_encryption(compose, compose->account, FALSE, s_system);
11445 if (compose && originally_sig && compose->account->default_sign_reply) {
11446 compose_force_signing(compose, compose->account, s_system);
11450 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11453 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
11456 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
11457 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
11458 GSList *cur = msginfo_list;
11459 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
11460 "messages. Opening the windows "
11461 "could take some time. Do you "
11462 "want to continue?"),
11463 g_slist_length(msginfo_list));
11464 if (g_slist_length(msginfo_list) > 9
11465 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
11466 != G_ALERTALTERNATE) {
11471 /* We'll open multiple compose windows */
11472 /* let the WM place the next windows */
11473 compose_force_window_origin = FALSE;
11474 for (; cur; cur = cur->next) {
11476 tmplist.data = cur->data;
11477 tmplist.next = NULL;
11478 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
11480 compose_force_window_origin = TRUE;
11482 /* forwarding multiple mails as attachments is done via a
11483 * single compose window */
11484 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
11488 void compose_check_for_email_account(Compose *compose)
11490 PrefsAccount *ac = NULL, *curr = NULL;
11496 if (compose->account && compose->account->protocol == A_NNTP) {
11497 ac = account_get_cur_account();
11498 if (ac->protocol == A_NNTP) {
11499 list = account_get_list();
11501 for( ; list != NULL ; list = g_list_next(list)) {
11502 curr = (PrefsAccount *) list->data;
11503 if (curr->protocol != A_NNTP) {
11509 combobox_select_by_data(GTK_COMBO_BOX(compose->account_combo),
11514 void compose_reply_to_address(MessageView *msgview, MsgInfo *msginfo,
11515 const gchar *address)
11517 GSList *msginfo_list = NULL;
11518 gchar *body = messageview_get_selection(msgview);
11521 msginfo_list = g_slist_prepend(msginfo_list, msginfo);
11523 compose = compose_reply_mode(COMPOSE_REPLY_TO_ADDRESS, msginfo_list, body);
11524 compose_check_for_email_account(compose);
11525 compose_set_folder_prefs(compose, msginfo->folder, FALSE);
11526 compose_entry_append(compose, address, COMPOSE_TO, PREF_NONE);
11527 compose_reply_set_subject(compose, msginfo);
11530 hooks_invoke(COMPOSE_CREATED_HOOKLIST, compose);
11533 void compose_set_position(Compose *compose, gint pos)
11535 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11537 gtkut_text_view_set_position(text, pos);
11540 gboolean compose_search_string(Compose *compose,
11541 const gchar *str, gboolean case_sens)
11543 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11545 return gtkut_text_view_search_string(text, str, case_sens);
11548 gboolean compose_search_string_backward(Compose *compose,
11549 const gchar *str, gboolean case_sens)
11551 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
11553 return gtkut_text_view_search_string_backward(text, str, case_sens);
11556 /* allocate a msginfo structure and populate its data from a compose data structure */
11557 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
11559 MsgInfo *newmsginfo;
11561 gchar buf[BUFFSIZE];
11563 cm_return_val_if_fail( compose != NULL, NULL );
11565 newmsginfo = procmsg_msginfo_new();
11568 get_rfc822_date(buf, sizeof(buf));
11569 newmsginfo->date = g_strdup(buf);
11572 if (compose->from_name) {
11573 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
11574 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
11578 if (compose->subject_entry)
11579 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
11581 /* to, cc, reply-to, newsgroups */
11582 for (list = compose->header_list; list; list = list->next) {
11583 gchar *header = gtk_editable_get_chars(
11585 gtk_bin_get_child(GTK_BIN((((ComposeHeaderEntry *)list->data)->combo)))), 0, -1);
11586 gchar *entry = gtk_editable_get_chars(
11587 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
11589 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
11590 if ( newmsginfo->to == NULL ) {
11591 newmsginfo->to = g_strdup(entry);
11592 } else if (entry && *entry) {
11593 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
11594 g_free(newmsginfo->to);
11595 newmsginfo->to = tmp;
11598 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
11599 if ( newmsginfo->cc == NULL ) {
11600 newmsginfo->cc = g_strdup(entry);
11601 } else if (entry && *entry) {
11602 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
11603 g_free(newmsginfo->cc);
11604 newmsginfo->cc = tmp;
11607 if ( strcasecmp(header,
11608 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
11609 if ( newmsginfo->newsgroups == NULL ) {
11610 newmsginfo->newsgroups = g_strdup(entry);
11611 } else if (entry && *entry) {
11612 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
11613 g_free(newmsginfo->newsgroups);
11614 newmsginfo->newsgroups = tmp;
11622 /* other data is unset */
11628 /* update compose's dictionaries from folder dict settings */
11629 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
11630 FolderItem *folder_item)
11632 cm_return_if_fail(compose != NULL);
11634 if (compose->gtkaspell && folder_item && folder_item->prefs) {
11635 FolderItemPrefs *prefs = folder_item->prefs;
11637 if (prefs->enable_default_dictionary)
11638 gtkaspell_change_dict(compose->gtkaspell,
11639 prefs->default_dictionary, FALSE);
11640 if (folder_item->prefs->enable_default_alt_dictionary)
11641 gtkaspell_change_alt_dict(compose->gtkaspell,
11642 prefs->default_alt_dictionary);
11643 if (prefs->enable_default_dictionary
11644 || prefs->enable_default_alt_dictionary)
11645 compose_spell_menu_changed(compose);