2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #ifndef PANGO_ENABLE_ENGINE
27 # define PANGO_ENABLE_ENGINE
31 #include <glib/gi18n.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtkmain.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkitemfactory.h>
37 #include <gtk/gtkcheckmenuitem.h>
38 #include <gtk/gtkoptionmenu.h>
39 #include <gtk/gtkwidget.h>
40 #include <gtk/gtkvpaned.h>
41 #include <gtk/gtkentry.h>
42 #include <gtk/gtkeditable.h>
43 #include <gtk/gtkwindow.h>
44 #include <gtk/gtksignal.h>
45 #include <gtk/gtkvbox.h>
46 #include <gtk/gtkcontainer.h>
47 #include <gtk/gtkhandlebox.h>
48 #include <gtk/gtktoolbar.h>
49 #include <gtk/gtktable.h>
50 #include <gtk/gtkhbox.h>
51 #include <gtk/gtklabel.h>
52 #include <gtk/gtkscrolledwindow.h>
53 #include <gtk/gtktreeview.h>
54 #include <gtk/gtkliststore.h>
55 #include <gtk/gtktreeselection.h>
56 #include <gtk/gtktreemodel.h>
58 #include <gtk/gtkdnd.h>
59 #include <gtk/gtkclipboard.h>
60 #include <pango/pango-break.h>
65 #include <sys/types.h>
71 # include <sys/wait.h>
75 #ifndef G_OS_WIN32 /* fixme we should have a configure test. */
79 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
86 #include "mainwindow.h"
88 #include "addressbook.h"
89 #include "folderview.h"
92 #include "stock_pixmap.h"
93 #include "send_message.h"
96 #include "customheader.h"
97 #include "prefs_common.h"
98 #include "prefs_account.h"
102 #include "procheader.h"
103 #include "procmime.h"
104 #include "statusbar.h"
107 #include "quoted-printable.h"
108 #include "codeconv.h"
110 #include "gtkutils.h"
112 #include "alertpanel.h"
113 #include "manage_window.h"
114 #include "gtkshruler.h"
116 #include "addr_compl.h"
117 #include "quote_fmt.h"
119 #include "foldersel.h"
122 #include "message_search.h"
123 #include "combobox.h"
127 #include "autofaces.h"
139 #define N_ATTACH_COLS (N_COL_COLUMNS)
143 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
144 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
145 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
146 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
147 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
148 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
149 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
150 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
151 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
152 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
153 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
154 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
155 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
156 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END
157 } ComposeCallAdvancedAction;
161 PRIORITY_HIGHEST = 1,
170 COMPOSE_INSERT_SUCCESS,
171 COMPOSE_INSERT_READ_ERROR,
172 COMPOSE_INSERT_INVALID_CHARACTER,
173 COMPOSE_INSERT_NO_FILE
174 } ComposeInsertResult;
178 COMPOSE_WRITE_FOR_SEND,
179 COMPOSE_WRITE_FOR_STORE
184 COMPOSE_QUOTE_FORCED,
189 #define B64_LINE_SIZE 57
190 #define B64_BUFFSIZE 77
192 #define MAX_REFERENCES_LEN 999
194 static GList *compose_list = NULL;
196 static Compose *compose_generic_new (PrefsAccount *account,
199 GPtrArray *attach_files,
200 GList *listAddress );
202 static Compose *compose_create (PrefsAccount *account,
207 static void compose_entry_mark_default_to (Compose *compose,
208 const gchar *address);
209 static Compose *compose_followup_and_reply_to (MsgInfo *msginfo,
210 ComposeQuoteMode quote_mode,
214 static Compose *compose_forward_multiple (PrefsAccount *account,
215 GSList *msginfo_list);
216 static Compose *compose_reply (MsgInfo *msginfo,
217 ComposeQuoteMode quote_mode,
222 static Compose *compose_reply_mode (ComposeMode mode,
223 GSList *msginfo_list,
225 static void compose_template_apply_fields(Compose *compose, Template *tmpl);
226 static void compose_update_privacy_systems_menu(Compose *compose);
228 static GtkWidget *compose_account_option_menu_create
230 static void compose_set_out_encoding (Compose *compose);
231 static void compose_set_template_menu (Compose *compose);
232 static void compose_template_apply (Compose *compose,
235 static void compose_destroy (Compose *compose);
237 static void compose_entries_set (Compose *compose,
239 ComposeEntryType to_type);
240 static gint compose_parse_header (Compose *compose,
242 static gchar *compose_parse_references (const gchar *ref,
245 static gchar *compose_quote_fmt (Compose *compose,
251 gboolean need_unescape,
252 const gchar *err_msg);
254 static void compose_reply_set_entry (Compose *compose,
260 followup_and_reply_to);
261 static void compose_reedit_set_entry (Compose *compose,
264 static void compose_insert_sig (Compose *compose,
266 static gchar *compose_get_signature_str (Compose *compose);
267 static ComposeInsertResult compose_insert_file (Compose *compose,
270 static gboolean compose_attach_append (Compose *compose,
273 const gchar *content_type);
274 static void compose_attach_parts (Compose *compose,
277 static gboolean compose_beautify_paragraph (Compose *compose,
278 GtkTextIter *par_iter,
280 static void compose_wrap_all (Compose *compose);
281 static void compose_wrap_all_full (Compose *compose,
284 static void compose_set_title (Compose *compose);
285 static void compose_select_account (Compose *compose,
286 PrefsAccount *account,
289 static PrefsAccount *compose_current_mail_account(void);
290 /* static gint compose_send (Compose *compose); */
291 static gboolean compose_check_for_valid_recipient
293 static gboolean compose_check_entries (Compose *compose,
294 gboolean check_everything);
295 static gint compose_write_to_file (Compose *compose,
298 gboolean attach_parts);
299 static gint compose_write_body_to_file (Compose *compose,
301 static gint compose_remove_reedit_target (Compose *compose,
303 static void compose_remove_draft (Compose *compose);
304 static gint compose_queue_sub (Compose *compose,
308 gboolean check_subject,
309 gboolean remove_reedit_target);
310 static void compose_add_attachments (Compose *compose,
312 static gchar *compose_get_header (Compose *compose);
314 static void compose_convert_header (Compose *compose,
319 gboolean addr_field);
321 static void compose_attach_info_free (AttachInfo *ainfo);
322 static void compose_attach_remove_selected (Compose *compose);
324 static void compose_attach_property (Compose *compose);
325 static void compose_attach_property_create (gboolean *cancelled);
326 static void attach_property_ok (GtkWidget *widget,
327 gboolean *cancelled);
328 static void attach_property_cancel (GtkWidget *widget,
329 gboolean *cancelled);
330 static gint attach_property_delete_event (GtkWidget *widget,
332 gboolean *cancelled);
333 static gboolean attach_property_key_pressed (GtkWidget *widget,
335 gboolean *cancelled);
337 static void compose_exec_ext_editor (Compose *compose);
339 static gint compose_exec_ext_editor_real (const gchar *file);
340 static gboolean compose_ext_editor_kill (Compose *compose);
341 static gboolean compose_input_cb (GIOChannel *source,
342 GIOCondition condition,
344 static void compose_set_ext_editor_sensitive (Compose *compose,
346 #endif /* G_OS_UNIX */
348 static void compose_undo_state_changed (UndoMain *undostruct,
353 static void compose_create_header_entry (Compose *compose);
354 static void compose_add_header_entry (Compose *compose, const gchar *header, gchar *text);
355 static void compose_remove_header_entries(Compose *compose);
357 static void compose_update_priority_menu_item(Compose * compose);
359 static void compose_spell_menu_changed (void *data);
361 static void compose_add_field_list ( Compose *compose,
362 GList *listAddress );
364 /* callback functions */
366 static gboolean compose_edit_size_alloc (GtkEditable *widget,
367 GtkAllocation *allocation,
368 GtkSHRuler *shruler);
369 static void account_activated (GtkComboBox *optmenu,
371 static void attach_selected (GtkTreeView *tree_view,
372 GtkTreePath *tree_path,
373 GtkTreeViewColumn *column,
375 static gboolean attach_button_pressed (GtkWidget *widget,
376 GdkEventButton *event,
378 static gboolean attach_key_pressed (GtkWidget *widget,
381 static void compose_send_cb (gpointer data,
384 static void compose_send_later_cb (gpointer data,
388 static void compose_draft_cb (gpointer data,
392 static void compose_attach_cb (gpointer data,
395 static void compose_insert_file_cb (gpointer data,
398 static void compose_insert_sig_cb (gpointer data,
402 static void compose_close_cb (gpointer data,
406 static void compose_set_encoding_cb (gpointer data,
410 static void compose_address_cb (gpointer data,
413 static void compose_template_activate_cb(GtkWidget *widget,
416 static void compose_ext_editor_cb (gpointer data,
420 static gint compose_delete_cb (GtkWidget *widget,
424 static void compose_undo_cb (Compose *compose);
425 static void compose_redo_cb (Compose *compose);
426 static void compose_cut_cb (Compose *compose);
427 static void compose_copy_cb (Compose *compose);
428 static void compose_paste_cb (Compose *compose);
429 static void compose_paste_as_quote_cb (Compose *compose);
430 static void compose_paste_no_wrap_cb (Compose *compose);
431 static void compose_paste_wrap_cb (Compose *compose);
432 static void compose_allsel_cb (Compose *compose);
434 static void compose_advanced_action_cb (Compose *compose,
435 ComposeCallAdvancedAction action);
437 static void compose_grab_focus_cb (GtkWidget *widget,
440 static void compose_changed_cb (GtkTextBuffer *textbuf,
443 static void compose_wrap_cb (gpointer data,
446 static void compose_find_cb (gpointer data,
449 static void compose_toggle_autowrap_cb (gpointer data,
453 static void compose_toggle_ruler_cb (gpointer data,
456 static void compose_toggle_sign_cb (gpointer data,
459 static void compose_toggle_encrypt_cb (gpointer data,
462 static void compose_set_privacy_system_cb(GtkWidget *widget,
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(gpointer data, guint action,
472 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
474 static void compose_set_priority_cb (gpointer data,
477 static void compose_reply_change_mode (gpointer data,
481 static void compose_attach_drag_received_cb (GtkWidget *widget,
482 GdkDragContext *drag_context,
485 GtkSelectionData *data,
489 static void compose_insert_drag_received_cb (GtkWidget *widget,
490 GdkDragContext *drag_context,
493 GtkSelectionData *data,
497 static void compose_header_drag_received_cb (GtkWidget *widget,
498 GdkDragContext *drag_context,
501 GtkSelectionData *data,
506 static gboolean compose_drag_drop (GtkWidget *widget,
507 GdkDragContext *drag_context,
509 guint time, gpointer user_data);
511 static void text_inserted (GtkTextBuffer *buffer,
516 static Compose *compose_generic_reply(MsgInfo *msginfo,
517 ComposeQuoteMode quote_mode,
521 gboolean followup_and_reply_to,
524 static gboolean compose_headerentry_changed_cb (GtkWidget *entry,
525 ComposeHeaderEntry *headerentry);
526 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
528 ComposeHeaderEntry *headerentry);
530 static void compose_show_first_last_header (Compose *compose, gboolean show_first);
532 static void compose_allow_user_actions (Compose *compose, gboolean allow);
535 static void compose_check_all (Compose *compose);
536 static void compose_highlight_all (Compose *compose);
537 static void compose_check_backwards (Compose *compose);
538 static void compose_check_forwards_go (Compose *compose);
541 static gint compose_defer_auto_save_draft (Compose *compose);
542 static PrefsAccount *compose_guess_forward_account_from_msginfo (MsgInfo *msginfo);
544 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose);
547 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
548 FolderItem *folder_item);
550 static void compose_attach_update_label(Compose *compose);
552 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data);
554 static GtkItemFactoryEntry compose_popup_entries[] =
556 {N_("/_Add..."), NULL, compose_attach_cb, 0, NULL},
557 {N_("/_Remove"), NULL, compose_attach_remove_selected, 0, NULL},
558 {"/---", NULL, NULL, 0, "<Separator>"},
559 {N_("/_Properties..."), NULL, compose_attach_property, 0, NULL}
562 static GtkItemFactoryEntry compose_entries[] =
564 {N_("/_Message"), NULL, NULL, 0, "<Branch>"},
565 {N_("/_Message/S_end"), "<control>Return",
566 compose_send_cb, 0, NULL},
567 {N_("/_Message/Send _later"), "<shift><control>S",
568 compose_send_later_cb, 0, NULL},
569 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
570 {N_("/_Message/_Attach file"), "<control>M", compose_attach_cb, 0, NULL},
571 {N_("/_Message/_Insert file"), "<control>I", compose_insert_file_cb, 0, NULL},
572 {N_("/_Message/Insert si_gnature"), "<control>G", compose_insert_sig_cb, 0, NULL},
573 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
574 {N_("/_Message/_Save"),
575 "<control>S", compose_draft_cb, COMPOSE_KEEP_EDITING, NULL},
576 {N_("/_Message/---"), NULL, NULL, 0, "<Separator>"},
577 {N_("/_Message/_Close"), "<control>W", compose_close_cb, 0, NULL},
579 {N_("/_Edit"), NULL, NULL, 0, "<Branch>"},
580 {N_("/_Edit/_Undo"), "<control>Z", compose_undo_cb, 0, NULL},
581 {N_("/_Edit/_Redo"), "<control>Y", compose_redo_cb, 0, NULL},
582 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
583 {N_("/_Edit/Cu_t"), "<control>X", compose_cut_cb, 0, NULL},
584 {N_("/_Edit/_Copy"), "<control>C", compose_copy_cb, 0, NULL},
585 {N_("/_Edit/_Paste"), "<control>V", compose_paste_cb, 0, NULL},
586 {N_("/_Edit/Special paste"), NULL, NULL, 0, "<Branch>"},
587 {N_("/_Edit/Special paste/as _quotation"),
588 NULL, compose_paste_as_quote_cb, 0, NULL},
589 {N_("/_Edit/Special paste/_wrapped"),
590 NULL, compose_paste_wrap_cb, 0, NULL},
591 {N_("/_Edit/Special paste/_unwrapped"),
592 NULL, compose_paste_no_wrap_cb, 0, NULL},
593 {N_("/_Edit/Select _all"), "<control>A", compose_allsel_cb, 0, NULL},
594 {N_("/_Edit/A_dvanced"), NULL, NULL, 0, "<Branch>"},
595 {N_("/_Edit/A_dvanced/Move a character backward"),
597 compose_advanced_action_cb,
598 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_CHARACTER,
600 {N_("/_Edit/A_dvanced/Move a character forward"),
602 compose_advanced_action_cb,
603 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_CHARACTER,
605 {N_("/_Edit/A_dvanced/Move a word backward"),
607 compose_advanced_action_cb,
608 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BACKWARD_WORD,
610 {N_("/_Edit/A_dvanced/Move a word forward"),
612 compose_advanced_action_cb,
613 COMPOSE_CALL_ADVANCED_ACTION_MOVE_FORWARD_WORD,
615 {N_("/_Edit/A_dvanced/Move to beginning of line"),
616 NULL, /* "<control>A" */
617 compose_advanced_action_cb,
618 COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE,
620 {N_("/_Edit/A_dvanced/Move to end of line"),
622 compose_advanced_action_cb,
623 COMPOSE_CALL_ADVANCED_ACTION_MOVE_END_OF_LINE,
625 {N_("/_Edit/A_dvanced/Move to previous line"),
627 compose_advanced_action_cb,
628 COMPOSE_CALL_ADVANCED_ACTION_MOVE_PREVIOUS_LINE,
630 {N_("/_Edit/A_dvanced/Move to next line"),
632 compose_advanced_action_cb,
633 COMPOSE_CALL_ADVANCED_ACTION_MOVE_NEXT_LINE,
635 {N_("/_Edit/A_dvanced/Delete a character backward"),
637 compose_advanced_action_cb,
638 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_CHARACTER,
640 {N_("/_Edit/A_dvanced/Delete a character forward"),
642 compose_advanced_action_cb,
643 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_CHARACTER,
645 {N_("/_Edit/A_dvanced/Delete a word backward"),
646 NULL, /* "<control>W" */
647 compose_advanced_action_cb,
648 COMPOSE_CALL_ADVANCED_ACTION_DELETE_BACKWARD_WORD,
650 {N_("/_Edit/A_dvanced/Delete a word forward"),
651 NULL, /* "<alt>D", */
652 compose_advanced_action_cb,
653 COMPOSE_CALL_ADVANCED_ACTION_DELETE_FORWARD_WORD,
655 {N_("/_Edit/A_dvanced/Delete line"),
657 compose_advanced_action_cb,
658 COMPOSE_CALL_ADVANCED_ACTION_DELETE_LINE,
660 {N_("/_Edit/A_dvanced/Delete to end of line"),
662 compose_advanced_action_cb,
663 COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END,
665 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
667 "<control>F", compose_find_cb, 0, NULL},
668 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
669 {N_("/_Edit/_Wrap current paragraph"),
670 "<control>L", compose_wrap_cb, 0, NULL},
671 {N_("/_Edit/Wrap all long _lines"),
672 "<control><alt>L", compose_wrap_cb, 1, NULL},
673 {N_("/_Edit/Aut_o wrapping"), "<shift><control>L", compose_toggle_autowrap_cb, 0, "<ToggleItem>"},
674 {N_("/_Edit/---"), NULL, NULL, 0, "<Separator>"},
675 {N_("/_Edit/Edit with e_xternal editor"),
676 "<shift><control>X", compose_ext_editor_cb, 0, NULL},
678 {N_("/_Spelling"), NULL, NULL, 0, "<Branch>"},
679 {N_("/_Spelling/_Check all or check selection"),
680 NULL, compose_check_all, 0, NULL},
681 {N_("/_Spelling/_Highlight all misspelled words"),
682 NULL, compose_highlight_all, 0, NULL},
683 {N_("/_Spelling/Check _backwards misspelled word"),
684 NULL, compose_check_backwards , 0, NULL},
685 {N_("/_Spelling/_Forward to next misspelled word"),
686 NULL, compose_check_forwards_go, 0, NULL},
687 {N_("/_Spelling/---"), NULL, NULL, 0, "<Separator>"},
688 {N_("/_Spelling/Options"),
689 NULL, NULL, 0, "<Branch>"},
691 {N_("/_Options"), NULL, NULL, 0, "<Branch>"},
692 {N_("/_Options/Reply _mode"), NULL, NULL, 0, "<Branch>"},
693 {N_("/_Options/Reply _mode/_Normal"), NULL, compose_reply_change_mode, COMPOSE_REPLY, "<RadioItem>"},
694 {N_("/_Options/Reply _mode/_All"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_ALL, "/Options/Reply mode/Normal"},
695 {N_("/_Options/Reply _mode/_Sender"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_SENDER, "/Options/Reply mode/Normal"},
696 {N_("/_Options/Reply _mode/_Mailing-list"), NULL, compose_reply_change_mode, COMPOSE_REPLY_TO_LIST, "/Options/Reply mode/Normal"},
697 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
698 {N_("/_Options/Privacy _System"), NULL, NULL, 0, "<Branch>"},
699 {N_("/_Options/Privacy _System/None"), NULL, NULL, 0, "<RadioItem>"},
700 {N_("/_Options/Si_gn"), NULL, compose_toggle_sign_cb , 0, "<ToggleItem>"},
701 {N_("/_Options/_Encrypt"), NULL, compose_toggle_encrypt_cb, 0, "<ToggleItem>"},
702 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
703 {N_("/_Options/_Priority"), NULL, NULL, 0, "<Branch>"},
704 {N_("/_Options/Priority/_Highest"), NULL, compose_set_priority_cb, PRIORITY_HIGHEST, "<RadioItem>"},
705 {N_("/_Options/Priority/Hi_gh"), NULL, compose_set_priority_cb, PRIORITY_HIGH, "/Options/Priority/Highest"},
706 {N_("/_Options/Priority/_Normal"), NULL, compose_set_priority_cb, PRIORITY_NORMAL, "/Options/Priority/Highest"},
707 {N_("/_Options/Priority/Lo_w"), NULL, compose_set_priority_cb, PRIORITY_LOW, "/Options/Priority/Highest"},
708 {N_("/_Options/Priority/_Lowest"), NULL, compose_set_priority_cb, PRIORITY_LOWEST, "/Options/Priority/Highest"},
709 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
710 {N_("/_Options/_Request Return Receipt"), NULL, compose_toggle_return_receipt_cb, 0, "<ToggleItem>"},
711 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
712 {N_("/_Options/Remo_ve references"), NULL, compose_toggle_remove_refs_cb, 0, "<ToggleItem>"},
713 {N_("/_Options/---"), NULL, NULL, 0, "<Separator>"},
715 #define ENC_ACTION(action) \
716 NULL, compose_set_encoding_cb, action, \
717 "/Options/Character encoding/Automatic"
719 {N_("/_Options/Character _encoding"), NULL, NULL, 0, "<Branch>"},
720 {N_("/_Options/Character _encoding/_Automatic"),
721 NULL, compose_set_encoding_cb, C_AUTO, "<RadioItem>"},
722 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
724 {N_("/_Options/Character _encoding/7bit ASCII (US-ASC_II)"),
725 ENC_ACTION(C_US_ASCII)},
726 {N_("/_Options/Character _encoding/Unicode (_UTF-8)"),
727 ENC_ACTION(C_UTF_8)},
728 {N_("/_Options/Character _encoding/---"), NULL, NULL, 0, "<Separator>"},
730 {N_("/_Options/Character _encoding/Western European"), NULL, NULL, 0, "<Branch>"},
731 {N_("/_Options/Character _encoding/Western European/ISO-8859-_1"),
732 ENC_ACTION(C_ISO_8859_1)},
733 {N_("/_Options/Character _encoding/Western European/ISO-8859-15"),
734 ENC_ACTION(C_ISO_8859_15)},
735 {N_("/_Options/Character _encoding/Western European/Windows-1252"),
736 ENC_ACTION(C_WINDOWS_1252)},
738 {N_("/_Options/Character _encoding/Central European (ISO-8859-_2)"),
739 ENC_ACTION(C_ISO_8859_2)},
741 {N_("/_Options/Character _encoding/Baltic"), NULL, NULL, 0, "<Branch>"},
742 {N_("/_Options/Character _encoding/Baltic/ISO-8859-13"),
743 ENC_ACTION(C_ISO_8859_13)},
744 {N_("/_Options/Character _encoding/Baltic/ISO-8859-_4"),
745 ENC_ACTION(C_ISO_8859_4)},
747 {N_("/_Options/Character _encoding/Greek (ISO-8859-_7)"),
748 ENC_ACTION(C_ISO_8859_7)},
750 {N_("/_Options/Character _encoding/Hebrew"), NULL, NULL, 0, "<Branch>"},
751 {N_("/_Options/Character _encoding/Hebrew/ISO-8859-_8"),
752 ENC_ACTION(C_ISO_8859_8)},
753 {N_("/_Options/Character _encoding/Hebrew/Windows-1255"),
754 ENC_ACTION(C_WINDOWS_1255)},
756 {N_("/_Options/Character _encoding/Arabic"), NULL, NULL, 0, "<Branch>"},
757 {N_("/_Options/Character _encoding/Arabic/ISO-8859-_6"),
758 ENC_ACTION(C_ISO_8859_6)},
759 {N_("/_Options/Character _encoding/Arabic/Windows-1256"),
760 ENC_ACTION(C_CP1256)},
762 {N_("/_Options/Character _encoding/Turkish (ISO-8859-_9)"),
763 ENC_ACTION(C_ISO_8859_9)},
765 {N_("/_Options/Character _encoding/Cyrillic"), NULL, NULL, 0, "<Branch>"},
766 {N_("/_Options/Character _encoding/Cyrillic/ISO-8859-_5"),
767 ENC_ACTION(C_ISO_8859_5)},
768 {N_("/_Options/Character _encoding/Cyrillic/KOI8-_R"),
769 ENC_ACTION(C_KOI8_R)},
770 {N_("/_Options/Character _encoding/Cyrillic/KOI8-U"),
771 ENC_ACTION(C_KOI8_U)},
772 {N_("/_Options/Character _encoding/Cyrillic/Windows-1251"),
773 ENC_ACTION(C_WINDOWS_1251)},
775 {N_("/_Options/Character _encoding/Japanese"), NULL, NULL, 0, "<Branch>"},
776 {N_("/_Options/Character _encoding/Japanese/ISO-2022-_JP"),
777 ENC_ACTION(C_ISO_2022_JP)},
778 {N_("/_Options/Character _encoding/Japanese/ISO-2022-JP-2"),
779 ENC_ACTION(C_ISO_2022_JP_2)},
780 {N_("/_Options/Character _encoding/Japanese/_EUC-JP"),
781 ENC_ACTION(C_EUC_JP)},
782 {N_("/_Options/Character _encoding/Japanese/_Shift__JIS"),
783 ENC_ACTION(C_SHIFT_JIS)},
785 {N_("/_Options/Character _encoding/Chinese"), NULL, NULL, 0, "<Branch>"},
786 {N_("/_Options/Character _encoding/Chinese/Simplified (_GB2312)"),
787 ENC_ACTION(C_GB2312)},
788 {N_("/_Options/Character _encoding/Chinese/Simplified (GBK)"),
790 {N_("/_Options/Character _encoding/Chinese/Traditional (_Big5)"),
792 {N_("/_Options/Character _encoding/Chinese/Traditional (EUC-_TW)"),
793 ENC_ACTION(C_EUC_TW)},
795 {N_("/_Options/Character _encoding/Korean"), NULL, NULL, 0, "<Branch>"},
796 {N_("/_Options/Character _encoding/Korean/EUC-_KR"),
797 ENC_ACTION(C_EUC_KR)},
798 {N_("/_Options/Character _encoding/Korean/ISO-2022-KR"),
799 ENC_ACTION(C_ISO_2022_KR)},
801 {N_("/_Options/Character _encoding/Thai"), NULL, NULL, 0, "<Branch>"},
802 {N_("/_Options/Character _encoding/Thai/TIS-620"),
803 ENC_ACTION(C_TIS_620)},
804 {N_("/_Options/Character _encoding/Thai/Windows-874"),
805 ENC_ACTION(C_WINDOWS_874)},
807 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
808 {N_("/_Tools/Show _ruler"), NULL, compose_toggle_ruler_cb, 0, "<ToggleItem>"},
809 {N_("/_Tools/_Address book"), "<shift><control>A", compose_address_cb , 0, NULL},
810 {N_("/_Tools/_Template"), NULL, NULL, 0, "<Branch>"},
811 {N_("/_Tools/Actio_ns"), NULL, NULL, 0, "<Branch>"},
812 {N_("/_Help"), NULL, NULL, 0, "<Branch>"},
813 {N_("/_Help/_About"), NULL, about_show, 0, NULL}
816 static GtkTargetEntry compose_mime_types[] =
818 {"text/uri-list", 0, 0},
819 {"UTF8_STRING", 0, 0},
823 static gboolean compose_put_existing_to_front(MsgInfo *info)
825 GList *compose_list = compose_get_compose_list();
829 for (elem = compose_list; elem != NULL && elem->data != NULL;
831 Compose *c = (Compose*)elem->data;
833 if (!c->targetinfo || !c->targetinfo->msgid ||
837 if (!strcmp(c->targetinfo->msgid, info->msgid)) {
838 gtkut_window_popup(c->window);
846 static GdkColor quote_color1 =
847 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
848 static GdkColor quote_color2 =
849 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
850 static GdkColor quote_color3 =
851 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
853 static GdkColor quote_bgcolor1 =
854 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
855 static GdkColor quote_bgcolor2 =
856 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
857 static GdkColor quote_bgcolor3 =
858 {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
860 static GdkColor signature_color = {
867 static GdkColor uri_color = {
874 static void compose_create_tags(GtkTextView *text, Compose *compose)
876 GtkTextBuffer *buffer;
877 GdkColor black = {(gulong)0, (gushort)0, (gushort)0, (gushort)0};
883 buffer = gtk_text_view_get_buffer(text);
885 if (prefs_common.enable_color) {
886 /* grab the quote colors, converting from an int to a GdkColor */
887 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_col,
889 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_col,
891 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_col,
893 gtkut_convert_int_to_gdk_color(prefs_common.quote_level1_bgcol,
895 gtkut_convert_int_to_gdk_color(prefs_common.quote_level2_bgcol,
897 gtkut_convert_int_to_gdk_color(prefs_common.quote_level3_bgcol,
899 gtkut_convert_int_to_gdk_color(prefs_common.signature_col,
901 gtkut_convert_int_to_gdk_color(prefs_common.uri_col,
904 signature_color = quote_color1 = quote_color2 = quote_color3 =
905 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = uri_color = black;
908 if (prefs_common.enable_color && prefs_common.enable_bgcolor) {
909 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
910 "foreground-gdk", "e_color1,
911 "paragraph-background-gdk", "e_bgcolor1,
913 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
914 "foreground-gdk", "e_color2,
915 "paragraph-background-gdk", "e_bgcolor2,
917 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
918 "foreground-gdk", "e_color3,
919 "paragraph-background-gdk", "e_bgcolor3,
922 compose->quote0_tag = gtk_text_buffer_create_tag(buffer, "quote0",
923 "foreground-gdk", "e_color1,
925 compose->quote1_tag = gtk_text_buffer_create_tag(buffer, "quote1",
926 "foreground-gdk", "e_color2,
928 compose->quote2_tag = gtk_text_buffer_create_tag(buffer, "quote2",
929 "foreground-gdk", "e_color3,
933 compose->signature_tag = gtk_text_buffer_create_tag(buffer, "signature",
934 "foreground-gdk", &signature_color,
937 compose->uri_tag = gtk_text_buffer_create_tag(buffer, "link",
938 "foreground-gdk", &uri_color,
940 compose->no_wrap_tag = gtk_text_buffer_create_tag(buffer, "no_wrap", NULL);
941 compose->no_join_tag = gtk_text_buffer_create_tag(buffer, "no_join", NULL);
943 color[0] = quote_color1;
944 color[1] = quote_color2;
945 color[2] = quote_color3;
946 color[3] = quote_bgcolor1;
947 color[4] = quote_bgcolor2;
948 color[5] = quote_bgcolor3;
949 color[6] = signature_color;
950 color[7] = uri_color;
951 cmap = gdk_drawable_get_colormap(compose->window->window);
952 gdk_colormap_alloc_colors(cmap, color, 8, FALSE, TRUE, success);
954 for (i = 0; i < 8; i++) {
955 if (success[i] == FALSE) {
958 g_warning("Compose: color allocation failed.\n");
959 style = gtk_widget_get_style(GTK_WIDGET(text));
960 quote_color1 = quote_color2 = quote_color3 =
961 quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 =
962 signature_color = uri_color = black;
967 Compose *compose_new(PrefsAccount *account, const gchar *mailto,
968 GPtrArray *attach_files)
970 return compose_generic_new(account, mailto, NULL, attach_files, NULL);
973 Compose *compose_new_with_folderitem(PrefsAccount *account, FolderItem *item, const gchar *mailto)
975 return compose_generic_new(account, mailto, item, NULL, NULL);
978 Compose *compose_new_with_list( PrefsAccount *account, GList *listAddress )
980 return compose_generic_new( account, NULL, NULL, NULL, listAddress );
983 #define SCROLL_TO_CURSOR(compose) { \
984 GtkTextMark *cmark = gtk_text_buffer_get_insert( \
985 gtk_text_view_get_buffer( \
986 GTK_TEXT_VIEW(compose->text))); \
987 gtk_text_view_scroll_mark_onscreen( \
988 GTK_TEXT_VIEW(compose->text), \
992 Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderItem *item,
993 GPtrArray *attach_files, GList *listAddress )
996 GtkTextView *textview;
997 GtkTextBuffer *textbuf;
999 GtkItemFactory *ifactory;
1000 const gchar *subject_format = NULL;
1001 const gchar *body_format = NULL;
1002 gchar *mailto_from = NULL;
1003 PrefsAccount *mailto_account = NULL;
1004 MsgInfo* dummyinfo = NULL;
1006 /* check if mailto defines a from */
1007 if (mailto && *mailto != '\0') {
1008 scan_mailto_url(mailto, &mailto_from, NULL, NULL, NULL, NULL, NULL, NULL);
1009 /* mailto defines a from, check if we can get account prefs from it,
1010 if not, the account prefs will be guessed using other ways, but we'll keep
1013 mailto_account = account_find_from_address(mailto_from, TRUE);
1015 account = mailto_account;
1018 /* if no account prefs set from mailto, set if from folder prefs (if any) */
1019 if (!mailto_account && item && item->prefs && item->prefs->enable_default_account)
1020 account = account_find_from_id(item->prefs->default_account);
1022 /* if no account prefs set, fallback to the current one */
1023 if (!account) account = cur_account;
1024 g_return_val_if_fail(account != NULL, NULL);
1026 compose = compose_create(account, item, COMPOSE_NEW, FALSE);
1028 /* override from name if mailto asked for it */
1030 gtk_entry_set_text(GTK_ENTRY(compose->from_name), mailto_from);
1031 g_free(mailto_from);
1033 /* override from name according to folder properties */
1034 if (item && item->prefs &&
1035 item->prefs->compose_with_format &&
1036 item->prefs->compose_override_from_format &&
1037 *item->prefs->compose_override_from_format != '\0') {
1042 dummyinfo = compose_msginfo_new_from_compose(compose);
1044 /* decode \-escape sequences in the internal representation of the quote format */
1045 tmp = malloc(strlen(item->prefs->compose_override_from_format)+1);
1046 pref_get_unescaped_pref(tmp, item->prefs->compose_override_from_format);
1049 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1050 compose->gtkaspell);
1052 quote_fmt_init(dummyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1054 quote_fmt_scan_string(tmp);
1057 buf = quote_fmt_get_buffer();
1059 alertpanel_error(_("New message From format error."));
1061 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1062 quote_fmt_reset_vartable();
1067 ifactory = gtk_item_factory_from_widget(compose->menubar);
1069 compose->replyinfo = NULL;
1070 compose->fwdinfo = NULL;
1072 textview = GTK_TEXT_VIEW(compose->text);
1073 textbuf = gtk_text_view_get_buffer(textview);
1074 compose_create_tags(textview, compose);
1076 undo_block(compose->undostruct);
1078 compose_set_dictionaries_from_folder_prefs(compose, item);
1081 if (account->auto_sig)
1082 compose_insert_sig(compose, FALSE);
1083 gtk_text_buffer_get_start_iter(textbuf, &iter);
1084 gtk_text_buffer_place_cursor(textbuf, &iter);
1086 if (account->protocol != A_NNTP) {
1087 if (mailto && *mailto != '\0') {
1088 compose_entries_set(compose, mailto, COMPOSE_TO);
1090 } else if (item && item->prefs->enable_default_to) {
1091 compose_entry_append(compose, item->prefs->default_to, COMPOSE_TO);
1092 compose_entry_mark_default_to(compose, item->prefs->default_to);
1094 if (item && item->ret_rcpt) {
1095 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1098 if (mailto && *mailto != '\0') {
1099 if (!strchr(mailto, '@'))
1100 compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
1102 compose_entries_set(compose, mailto, COMPOSE_TO);
1103 } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
1104 compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS);
1107 * CLAWS: just don't allow return receipt request, even if the user
1108 * may want to send an email. simple but foolproof.
1110 menu_set_sensitive(ifactory, "/Options/Request Return Receipt", FALSE);
1112 compose_add_field_list( compose, listAddress );
1114 if (item && item->prefs && item->prefs->compose_with_format) {
1115 subject_format = item->prefs->compose_subject_format;
1116 body_format = item->prefs->compose_body_format;
1117 } else if (account->compose_with_format) {
1118 subject_format = account->compose_subject_format;
1119 body_format = account->compose_body_format;
1120 } else if (prefs_common.compose_with_format) {
1121 subject_format = prefs_common.compose_subject_format;
1122 body_format = prefs_common.compose_body_format;
1125 if (subject_format || body_format) {
1128 && *subject_format != '\0' )
1130 gchar *subject = NULL;
1135 dummyinfo = compose_msginfo_new_from_compose(compose);
1137 /* decode \-escape sequences in the internal representation of the quote format */
1138 tmp = malloc(strlen(subject_format)+1);
1139 pref_get_unescaped_pref(tmp, subject_format);
1141 subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
1143 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE,
1144 compose->gtkaspell);
1146 quote_fmt_init(dummyinfo, NULL, subject, FALSE, compose->account, FALSE);
1148 quote_fmt_scan_string(tmp);
1151 buf = quote_fmt_get_buffer();
1153 alertpanel_error(_("New message subject format error."));
1155 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
1156 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1157 quote_fmt_reset_vartable();
1164 && *body_format != '\0' )
1167 GtkTextBuffer *buffer;
1168 GtkTextIter start, end;
1172 dummyinfo = compose_msginfo_new_from_compose(compose);
1174 text = GTK_TEXT_VIEW(compose->text);
1175 buffer = gtk_text_view_get_buffer(text);
1176 gtk_text_buffer_get_start_iter(buffer, &start);
1177 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
1178 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
1180 compose_quote_fmt(compose, dummyinfo,
1182 NULL, tmp, FALSE, TRUE,
1183 _("New message body format error at line %d."));
1184 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1185 quote_fmt_reset_vartable();
1191 procmsg_msginfo_free( dummyinfo );
1197 for (i = 0; i < attach_files->len; i++) {
1198 file = g_ptr_array_index(attach_files, i);
1199 compose_attach_append(compose, file, file, NULL);
1203 compose_show_first_last_header(compose, TRUE);
1205 /* Set save folder */
1206 if (item && item->prefs && item->prefs->save_copy_to_folder) {
1207 gchar *folderidentifier;
1209 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
1210 folderidentifier = folder_item_get_identifier(item);
1211 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1212 g_free(folderidentifier);
1215 gtk_widget_grab_focus(compose->header_last->entry);
1217 undo_unblock(compose->undostruct);
1219 if (prefs_common.auto_exteditor)
1220 compose_exec_ext_editor(compose);
1222 compose->draft_timeout_tag = -1;
1223 SCROLL_TO_CURSOR(compose);
1225 compose->modified = FALSE;
1226 compose_set_title(compose);
1230 static void compose_force_encryption(Compose *compose, PrefsAccount *account,
1231 gboolean override_pref)
1233 gchar *privacy = NULL;
1235 g_return_if_fail(compose != NULL);
1236 g_return_if_fail(account != NULL);
1238 if (override_pref == FALSE && account->default_encrypt_reply == FALSE)
1241 if (account->default_privacy_system
1242 && strlen(account->default_privacy_system)) {
1243 privacy = account->default_privacy_system;
1245 GSList *privacy_avail = privacy_get_system_ids();
1246 if (privacy_avail && g_slist_length(privacy_avail)) {
1247 privacy = (gchar *)(privacy_avail->data);
1250 if (privacy != NULL) {
1251 if (compose->privacy_system == NULL)
1252 compose->privacy_system = g_strdup(privacy);
1253 else if (*(compose->privacy_system) == '\0') {
1254 g_free(compose->privacy_system);
1255 compose->privacy_system = g_strdup(privacy);
1257 compose_update_privacy_system_menu_item(compose, FALSE);
1258 compose_use_encryption(compose, TRUE);
1262 static void compose_force_signing(Compose *compose, PrefsAccount *account)
1264 gchar *privacy = NULL;
1266 if (account->default_privacy_system
1267 && strlen(account->default_privacy_system)) {
1268 privacy = account->default_privacy_system;
1270 GSList *privacy_avail = privacy_get_system_ids();
1271 if (privacy_avail && g_slist_length(privacy_avail)) {
1272 privacy = (gchar *)(privacy_avail->data);
1275 if (privacy != NULL) {
1276 if (compose->privacy_system == NULL)
1277 compose->privacy_system = g_strdup(privacy);
1278 compose_update_privacy_system_menu_item(compose, FALSE);
1279 compose_use_signing(compose, TRUE);
1283 static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar *body)
1287 Compose *compose = NULL;
1288 GtkItemFactory *ifactory = NULL;
1290 g_return_val_if_fail(msginfo_list != NULL, NULL);
1292 msginfo = (MsgInfo*)g_slist_nth_data(msginfo_list, 0);
1293 g_return_val_if_fail(msginfo != NULL, NULL);
1295 list_len = g_slist_length(msginfo_list);
1299 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1300 FALSE, prefs_common.default_reply_list, FALSE, body);
1302 case COMPOSE_REPLY_WITH_QUOTE:
1303 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1304 FALSE, prefs_common.default_reply_list, FALSE, body);
1306 case COMPOSE_REPLY_WITHOUT_QUOTE:
1307 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1308 FALSE, prefs_common.default_reply_list, FALSE, NULL);
1310 case COMPOSE_REPLY_TO_SENDER:
1311 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1312 FALSE, FALSE, TRUE, body);
1314 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1315 compose = compose_followup_and_reply_to(msginfo,
1316 COMPOSE_QUOTE_CHECK,
1317 FALSE, FALSE, body);
1319 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1320 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1321 FALSE, FALSE, TRUE, body);
1323 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1324 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1325 FALSE, FALSE, TRUE, NULL);
1327 case COMPOSE_REPLY_TO_ALL:
1328 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1329 TRUE, FALSE, FALSE, body);
1331 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1332 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1333 TRUE, FALSE, FALSE, body);
1335 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1336 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1337 TRUE, FALSE, FALSE, NULL);
1339 case COMPOSE_REPLY_TO_LIST:
1340 compose = compose_reply(msginfo, COMPOSE_QUOTE_CHECK,
1341 FALSE, TRUE, FALSE, body);
1343 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1344 compose = compose_reply(msginfo, COMPOSE_QUOTE_FORCED,
1345 FALSE, TRUE, FALSE, body);
1347 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1348 compose = compose_reply(msginfo, COMPOSE_QUOTE_SKIP,
1349 FALSE, TRUE, FALSE, NULL);
1351 case COMPOSE_FORWARD:
1352 if (prefs_common.forward_as_attachment) {
1353 compose = compose_reply_mode(COMPOSE_FORWARD_AS_ATTACH, msginfo_list, body);
1356 compose = compose_reply_mode(COMPOSE_FORWARD_INLINE, msginfo_list, body);
1360 case COMPOSE_FORWARD_INLINE:
1361 /* check if we reply to more than one Message */
1362 if (list_len == 1) {
1363 compose = compose_forward(NULL, msginfo, FALSE, body, FALSE, FALSE);
1366 /* more messages FALL THROUGH */
1367 case COMPOSE_FORWARD_AS_ATTACH:
1368 compose = compose_forward_multiple(NULL, msginfo_list);
1370 case COMPOSE_REDIRECT:
1371 compose = compose_redirect(NULL, msginfo, FALSE);
1374 g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
1377 if (compose == NULL) {
1378 alertpanel_error(_("Unable to reply. The original email probably doesn't exist."));
1381 ifactory = gtk_item_factory_from_widget(compose->menubar);
1383 compose->rmode = mode;
1384 switch (compose->rmode) {
1386 case COMPOSE_REPLY_WITH_QUOTE:
1387 case COMPOSE_REPLY_WITHOUT_QUOTE:
1388 case COMPOSE_FOLLOWUP_AND_REPLY_TO:
1389 debug_print("reply mode Normal\n");
1390 menu_set_active(ifactory, "/Options/Reply mode/Normal", TRUE);
1391 compose_reply_change_mode(compose, COMPOSE_REPLY, NULL); /* force update */
1393 case COMPOSE_REPLY_TO_SENDER:
1394 case COMPOSE_REPLY_TO_SENDER_WITH_QUOTE:
1395 case COMPOSE_REPLY_TO_SENDER_WITHOUT_QUOTE:
1396 debug_print("reply mode Sender\n");
1397 menu_set_active(ifactory, "/Options/Reply mode/Sender", TRUE);
1399 case COMPOSE_REPLY_TO_ALL:
1400 case COMPOSE_REPLY_TO_ALL_WITH_QUOTE:
1401 case COMPOSE_REPLY_TO_ALL_WITHOUT_QUOTE:
1402 debug_print("reply mode All\n");
1403 menu_set_active(ifactory, "/Options/Reply mode/All", TRUE);
1405 case COMPOSE_REPLY_TO_LIST:
1406 case COMPOSE_REPLY_TO_LIST_WITH_QUOTE:
1407 case COMPOSE_REPLY_TO_LIST_WITHOUT_QUOTE:
1408 debug_print("reply mode List\n");
1409 menu_set_active(ifactory, "/Options/Reply mode/Mailing-list", TRUE);
1417 static Compose *compose_reply(MsgInfo *msginfo,
1418 ComposeQuoteMode quote_mode,
1424 return compose_generic_reply(msginfo, quote_mode, to_all, to_ml,
1425 to_sender, FALSE, body);
1428 static Compose *compose_followup_and_reply_to(MsgInfo *msginfo,
1429 ComposeQuoteMode quote_mode,
1434 return compose_generic_reply(msginfo, quote_mode, to_all, FALSE,
1435 to_sender, TRUE, body);
1438 static void compose_extract_original_charset(Compose *compose)
1440 MsgInfo *info = NULL;
1441 if (compose->replyinfo) {
1442 info = compose->replyinfo;
1443 } else if (compose->fwdinfo) {
1444 info = compose->fwdinfo;
1445 } else if (compose->targetinfo) {
1446 info = compose->targetinfo;
1449 MimeInfo *mimeinfo = procmime_scan_message_short(info);
1450 MimeInfo *partinfo = mimeinfo;
1451 while (partinfo && partinfo->type != MIMETYPE_TEXT)
1452 partinfo = procmime_mimeinfo_next(partinfo);
1454 compose->orig_charset =
1455 g_strdup(procmime_mimeinfo_get_parameter(
1456 partinfo, "charset"));
1458 procmime_mimeinfo_free_all(mimeinfo);
1462 #define SIGNAL_BLOCK(buffer) { \
1463 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1464 G_CALLBACK(compose_changed_cb), \
1466 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
1467 G_CALLBACK(text_inserted), \
1471 #define SIGNAL_UNBLOCK(buffer) { \
1472 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1473 G_CALLBACK(compose_changed_cb), \
1475 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
1476 G_CALLBACK(text_inserted), \
1480 static Compose *compose_generic_reply(MsgInfo *msginfo,
1481 ComposeQuoteMode quote_mode,
1482 gboolean to_all, gboolean to_ml,
1484 gboolean followup_and_reply_to,
1487 GtkItemFactory *ifactory;
1489 PrefsAccount *account = NULL;
1490 GtkTextView *textview;
1491 GtkTextBuffer *textbuf;
1492 gboolean quote = FALSE;
1493 const gchar *qmark = NULL;
1494 const gchar *body_fmt = NULL;
1496 g_return_val_if_fail(msginfo != NULL, NULL);
1497 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1499 account = account_get_reply_account(msginfo, prefs_common.reply_account_autosel);
1501 g_return_val_if_fail(account != NULL, NULL);
1503 compose = compose_create(account, msginfo->folder, COMPOSE_REPLY, FALSE);
1505 compose->updating = TRUE;
1507 ifactory = gtk_item_factory_from_widget(compose->menubar);
1509 menu_set_active(ifactory, "/Options/Remove references", FALSE);
1510 menu_set_sensitive(ifactory, "/Options/Remove references", TRUE);
1512 compose->replyinfo = procmsg_msginfo_get_full_info(msginfo);
1513 if (!compose->replyinfo)
1514 compose->replyinfo = procmsg_msginfo_copy(msginfo);
1516 compose_extract_original_charset(compose);
1518 if (msginfo->folder && msginfo->folder->ret_rcpt)
1519 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
1521 /* Set save folder */
1522 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1523 gchar *folderidentifier;
1525 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1526 folderidentifier = folder_item_get_identifier(msginfo->folder);
1527 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1528 g_free(folderidentifier);
1531 if (compose_parse_header(compose, msginfo) < 0) {
1532 compose->updating = FALSE;
1533 compose_destroy(compose);
1537 /* override from name according to folder properties */
1538 if (msginfo->folder && msginfo->folder->prefs &&
1539 msginfo->folder->prefs->reply_with_format &&
1540 msginfo->folder->prefs->reply_override_from_format &&
1541 *msginfo->folder->prefs->reply_override_from_format != '\0') {
1546 /* decode \-escape sequences in the internal representation of the quote format */
1547 tmp = malloc(strlen(msginfo->folder->prefs->reply_override_from_format)+1);
1548 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->reply_override_from_format);
1551 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE,
1552 compose->gtkaspell);
1554 quote_fmt_init(compose->replyinfo, NULL, NULL, FALSE, compose->account, FALSE);
1556 quote_fmt_scan_string(tmp);
1559 buf = quote_fmt_get_buffer();
1561 alertpanel_error(_("Message reply From format error."));
1563 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1564 quote_fmt_reset_vartable();
1569 textview = (GTK_TEXT_VIEW(compose->text));
1570 textbuf = gtk_text_view_get_buffer(textview);
1571 compose_create_tags(textview, compose);
1573 undo_block(compose->undostruct);
1575 compose_set_dictionaries_from_folder_prefs(compose, msginfo->folder);
1578 if (quote_mode == COMPOSE_QUOTE_FORCED ||
1579 (quote_mode == COMPOSE_QUOTE_CHECK && prefs_common.reply_with_quote)) {
1580 /* use the reply format of folder (if enabled), or the account's one
1581 (if enabled) or fallback to the global reply format, which is always
1582 enabled (even if empty), and use the relevant quotemark */
1584 if (msginfo->folder && msginfo->folder->prefs &&
1585 msginfo->folder->prefs->reply_with_format) {
1586 qmark = msginfo->folder->prefs->reply_quotemark;
1587 body_fmt = msginfo->folder->prefs->reply_body_format;
1589 } else if (account->reply_with_format) {
1590 qmark = account->reply_quotemark;
1591 body_fmt = account->reply_body_format;
1594 qmark = prefs_common.quotemark;
1595 if (prefs_common.quotefmt && *prefs_common.quotefmt)
1596 body_fmt = gettext(prefs_common.quotefmt);
1603 /* empty quotemark is not allowed */
1604 if (qmark == NULL || *qmark == '\0')
1606 compose_quote_fmt(compose, compose->replyinfo,
1607 body_fmt, qmark, body, FALSE, TRUE,
1608 _("Message reply format error at line %d."));
1609 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1610 quote_fmt_reset_vartable();
1613 if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
1614 compose_force_encryption(compose, account, FALSE);
1617 SIGNAL_BLOCK(textbuf);
1619 if (account->auto_sig)
1620 compose_insert_sig(compose, FALSE);
1622 compose_wrap_all(compose);
1624 SIGNAL_UNBLOCK(textbuf);
1626 gtk_widget_grab_focus(compose->text);
1628 undo_unblock(compose->undostruct);
1630 if (prefs_common.auto_exteditor)
1631 compose_exec_ext_editor(compose);
1633 compose->modified = FALSE;
1634 compose_set_title(compose);
1636 compose->updating = FALSE;
1637 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1638 SCROLL_TO_CURSOR(compose);
1640 if (compose->deferred_destroy) {
1641 compose_destroy(compose);
1648 #define INSERT_FW_HEADER(var, hdr) \
1649 if (msginfo->var && *msginfo->var) { \
1650 gtk_stext_insert(text, NULL, NULL, NULL, hdr, -1); \
1651 gtk_stext_insert(text, NULL, NULL, NULL, msginfo->var, -1); \
1652 gtk_stext_insert(text, NULL, NULL, NULL, "\n", 1); \
1655 Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
1656 gboolean as_attach, const gchar *body,
1657 gboolean no_extedit,
1661 GtkTextView *textview;
1662 GtkTextBuffer *textbuf;
1665 g_return_val_if_fail(msginfo != NULL, NULL);
1666 g_return_val_if_fail(msginfo->folder != NULL, NULL);
1669 !(account = compose_guess_forward_account_from_msginfo
1671 account = cur_account;
1673 compose = compose_create(account, msginfo->folder, COMPOSE_FORWARD, batch);
1675 compose->updating = TRUE;
1676 compose->fwdinfo = procmsg_msginfo_get_full_info(msginfo);
1677 if (!compose->fwdinfo)
1678 compose->fwdinfo = procmsg_msginfo_copy(msginfo);
1680 compose_extract_original_charset(compose);
1682 if (msginfo->subject && *msginfo->subject) {
1683 gchar *buf, *buf2, *p;
1685 buf = p = g_strdup(msginfo->subject);
1686 p += subject_get_prefix_length(p);
1687 memmove(buf, p, strlen(p) + 1);
1689 buf2 = g_strdup_printf("Fw: %s", buf);
1690 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1696 /* override from name according to folder properties */
1697 if (msginfo->folder && msginfo->folder->prefs &&
1698 msginfo->folder->prefs->forward_with_format &&
1699 msginfo->folder->prefs->forward_override_from_format &&
1700 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1704 MsgInfo *full_msginfo = NULL;
1707 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1709 full_msginfo = procmsg_msginfo_copy(msginfo);
1711 /* decode \-escape sequences in the internal representation of the quote format */
1712 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1713 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1716 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1717 compose->gtkaspell);
1719 quote_fmt_init(full_msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1721 quote_fmt_scan_string(tmp);
1724 buf = quote_fmt_get_buffer();
1726 alertpanel_error(_("Message forward From format error."));
1728 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1729 quote_fmt_reset_vartable();
1732 procmsg_msginfo_free(full_msginfo);
1735 textview = GTK_TEXT_VIEW(compose->text);
1736 textbuf = gtk_text_view_get_buffer(textview);
1737 compose_create_tags(textview, compose);
1739 undo_block(compose->undostruct);
1743 msgfile = procmsg_get_message_file(msginfo);
1744 if (!is_file_exist(msgfile))
1745 g_warning("%s: file not exist\n", msgfile);
1747 compose_attach_append(compose, msgfile, msgfile,
1752 const gchar *qmark = NULL;
1753 const gchar *body_fmt = NULL;
1754 MsgInfo *full_msginfo;
1756 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1757 body_fmt = gettext(prefs_common.fw_quotefmt);
1761 full_msginfo = procmsg_msginfo_get_full_info(msginfo);
1763 full_msginfo = procmsg_msginfo_copy(msginfo);
1765 /* use the forward format of folder (if enabled), or the account's one
1766 (if enabled) or fallback to the global forward format, which is always
1767 enabled (even if empty), and use the relevant quotemark */
1768 if (msginfo->folder && msginfo->folder->prefs &&
1769 msginfo->folder->prefs->forward_with_format) {
1770 qmark = msginfo->folder->prefs->forward_quotemark;
1771 body_fmt = msginfo->folder->prefs->forward_body_format;
1773 } else if (account->forward_with_format) {
1774 qmark = account->forward_quotemark;
1775 body_fmt = account->forward_body_format;
1778 qmark = prefs_common.fw_quotemark;
1779 if (prefs_common.fw_quotefmt && *prefs_common.fw_quotefmt)
1780 body_fmt = gettext(prefs_common.fw_quotefmt);
1785 /* empty quotemark is not allowed */
1786 if (qmark == NULL || *qmark == '\0')
1789 compose_quote_fmt(compose, full_msginfo,
1790 body_fmt, qmark, body, FALSE, TRUE,
1791 _("Message forward format error at line %d."));
1792 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
1793 quote_fmt_reset_vartable();
1794 compose_attach_parts(compose, msginfo);
1796 procmsg_msginfo_free(full_msginfo);
1799 SIGNAL_BLOCK(textbuf);
1801 if (account->auto_sig)
1802 compose_insert_sig(compose, FALSE);
1804 compose_wrap_all(compose);
1806 SIGNAL_UNBLOCK(textbuf);
1808 gtk_text_buffer_get_start_iter(textbuf, &iter);
1809 gtk_text_buffer_place_cursor(textbuf, &iter);
1811 gtk_widget_grab_focus(compose->header_last->entry);
1813 if (!no_extedit && prefs_common.auto_exteditor)
1814 compose_exec_ext_editor(compose);
1817 if (msginfo->folder && msginfo->folder->prefs && msginfo->folder->prefs->save_copy_to_folder) {
1818 gchar *folderidentifier;
1820 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
1821 folderidentifier = folder_item_get_identifier(msginfo->folder);
1822 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
1823 g_free(folderidentifier);
1826 undo_unblock(compose->undostruct);
1828 compose->modified = FALSE;
1829 compose_set_title(compose);
1831 compose->updating = FALSE;
1832 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1833 SCROLL_TO_CURSOR(compose);
1835 if (compose->deferred_destroy) {
1836 compose_destroy(compose);
1843 #undef INSERT_FW_HEADER
1845 static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_list)
1848 GtkTextView *textview;
1849 GtkTextBuffer *textbuf;
1853 gboolean single_mail = TRUE;
1855 g_return_val_if_fail(msginfo_list != NULL, NULL);
1857 if (g_slist_length(msginfo_list) > 1)
1858 single_mail = FALSE;
1860 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next)
1861 if (((MsgInfo *)msginfo->data)->folder == NULL)
1864 /* guess account from first selected message */
1866 !(account = compose_guess_forward_account_from_msginfo
1867 (msginfo_list->data)))
1868 account = cur_account;
1870 g_return_val_if_fail(account != NULL, NULL);
1872 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1873 MSG_UNSET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_REPLIED);
1874 MSG_SET_PERM_FLAGS(((MsgInfo *)msginfo->data)->flags, MSG_FORWARDED);
1877 compose = compose_create(account, ((MsgInfo *)msginfo_list->data)->folder, COMPOSE_FORWARD, FALSE);
1879 compose->updating = TRUE;
1881 /* override from name according to folder properties */
1882 if (msginfo_list->data) {
1883 MsgInfo *msginfo = msginfo_list->data;
1885 if (msginfo->folder && msginfo->folder->prefs &&
1886 msginfo->folder->prefs->forward_with_format &&
1887 msginfo->folder->prefs->forward_override_from_format &&
1888 *msginfo->folder->prefs->forward_override_from_format != '\0') {
1893 /* decode \-escape sequences in the internal representation of the quote format */
1894 tmp = malloc(strlen(msginfo->folder->prefs->forward_override_from_format)+1);
1895 pref_get_unescaped_pref(tmp, msginfo->folder->prefs->forward_override_from_format);
1898 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
1899 compose->gtkaspell);
1901 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
1903 quote_fmt_scan_string(tmp);
1906 buf = quote_fmt_get_buffer();
1908 alertpanel_error(_("Message forward From format error."));
1910 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
1911 quote_fmt_reset_vartable();
1917 textview = GTK_TEXT_VIEW(compose->text);
1918 textbuf = gtk_text_view_get_buffer(textview);
1919 compose_create_tags(textview, compose);
1921 undo_block(compose->undostruct);
1922 for (msginfo = msginfo_list; msginfo != NULL; msginfo = msginfo->next) {
1923 msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
1925 if (!is_file_exist(msgfile))
1926 g_warning("%s: file not exist\n", msgfile);
1928 compose_attach_append(compose, msgfile, msgfile,
1934 MsgInfo *info = (MsgInfo *)msginfo_list->data;
1935 if (info->subject && *info->subject) {
1936 gchar *buf, *buf2, *p;
1938 buf = p = g_strdup(info->subject);
1939 p += subject_get_prefix_length(p);
1940 memmove(buf, p, strlen(p) + 1);
1942 buf2 = g_strdup_printf("Fw: %s", buf);
1943 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
1949 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
1950 _("Fw: multiple emails"));
1953 SIGNAL_BLOCK(textbuf);
1955 if (account->auto_sig)
1956 compose_insert_sig(compose, FALSE);
1958 compose_wrap_all(compose);
1960 SIGNAL_UNBLOCK(textbuf);
1962 gtk_text_buffer_get_start_iter(textbuf, &iter);
1963 gtk_text_buffer_place_cursor(textbuf, &iter);
1965 gtk_widget_grab_focus(compose->header_last->entry);
1966 undo_unblock(compose->undostruct);
1967 compose->modified = FALSE;
1968 compose_set_title(compose);
1970 compose->updating = FALSE;
1971 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
1972 SCROLL_TO_CURSOR(compose);
1974 if (compose->deferred_destroy) {
1975 compose_destroy(compose);
1982 static gboolean compose_is_sig_separator(Compose *compose, GtkTextBuffer *textbuf, GtkTextIter *iter)
1984 GtkTextIter start = *iter;
1985 GtkTextIter end_iter;
1986 int start_pos = gtk_text_iter_get_offset(&start);
1988 if (!compose->account->sig_sep)
1991 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
1992 start_pos+strlen(compose->account->sig_sep));
1994 /* check sig separator */
1995 str = gtk_text_iter_get_text(&start, &end_iter);
1996 if (!strcmp(str, compose->account->sig_sep)) {
1998 /* check end of line (\n) */
1999 gtk_text_buffer_get_iter_at_offset(textbuf, &start,
2000 start_pos+strlen(compose->account->sig_sep));
2001 gtk_text_buffer_get_iter_at_offset(textbuf, &end_iter,
2002 start_pos+strlen(compose->account->sig_sep)+1);
2003 tmp = gtk_text_iter_get_text(&start, &end_iter);
2004 if (!strcmp(tmp,"\n")) {
2016 static void compose_colorize_signature(Compose *compose)
2018 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
2020 GtkTextIter end_iter;
2021 gtk_text_buffer_get_start_iter(buffer, &iter);
2022 while (gtk_text_iter_forward_line(&iter))
2023 if (compose_is_sig_separator(compose, buffer, &iter)) {
2024 gtk_text_buffer_get_end_iter(buffer, &end_iter);
2025 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &end_iter);
2029 #define BLOCK_WRAP() { \
2030 prev_autowrap = compose->autowrap; \
2031 buffer = gtk_text_view_get_buffer( \
2032 GTK_TEXT_VIEW(compose->text)); \
2033 compose->autowrap = FALSE; \
2035 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2036 G_CALLBACK(compose_changed_cb), \
2038 g_signal_handlers_block_by_func(G_OBJECT(buffer), \
2039 G_CALLBACK(text_inserted), \
2042 #define UNBLOCK_WRAP() { \
2043 compose->autowrap = prev_autowrap; \
2044 if (compose->autowrap) { \
2045 gint old = compose->draft_timeout_tag; \
2046 compose->draft_timeout_tag = -2; \
2047 compose_wrap_all(compose); \
2048 compose->draft_timeout_tag = old; \
2051 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2052 G_CALLBACK(compose_changed_cb), \
2054 g_signal_handlers_unblock_by_func(G_OBJECT(buffer), \
2055 G_CALLBACK(text_inserted), \
2059 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
2061 Compose *compose = NULL;
2062 PrefsAccount *account = NULL;
2063 GtkTextView *textview;
2064 GtkTextBuffer *textbuf;
2068 gchar buf[BUFFSIZE];
2069 gboolean use_signing = FALSE;
2070 gboolean use_encryption = FALSE;
2071 gchar *privacy_system = NULL;
2072 int priority = PRIORITY_NORMAL;
2073 MsgInfo *replyinfo = NULL, *fwdinfo = NULL;
2075 g_return_val_if_fail(msginfo != NULL, NULL);
2076 g_return_val_if_fail(msginfo->folder != NULL, NULL);
2078 if (compose_put_existing_to_front(msginfo)) {
2082 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2083 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2084 gchar queueheader_buf[BUFFSIZE];
2087 /* Select Account from queue headers */
2088 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2089 sizeof(queueheader_buf), "X-Claws-Account-Id:")) {
2090 id = atoi(&queueheader_buf[strlen("X-Claws-Account-Id:")]);
2091 account = account_find_from_id(id);
2093 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2094 sizeof(queueheader_buf), "X-Sylpheed-Account-Id:")) {
2095 id = atoi(&queueheader_buf[strlen("X-Sylpheed-Account-Id:")]);
2096 account = account_find_from_id(id);
2098 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2099 sizeof(queueheader_buf), "NAID:")) {
2100 id = atoi(&queueheader_buf[strlen("NAID:")]);
2101 account = account_find_from_id(id);
2103 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2104 sizeof(queueheader_buf), "MAID:")) {
2105 id = atoi(&queueheader_buf[strlen("MAID:")]);
2106 account = account_find_from_id(id);
2108 if (!account && !procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2109 sizeof(queueheader_buf), "S:")) {
2110 account = account_find_from_address(queueheader_buf, FALSE);
2112 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2113 sizeof(queueheader_buf), "X-Claws-Sign:")) {
2114 param = atoi(&queueheader_buf[strlen("X-Claws-Sign:")]);
2115 use_signing = param;
2118 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2119 sizeof(queueheader_buf), "X-Sylpheed-Sign:")) {
2120 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Sign:")]);
2121 use_signing = param;
2124 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2125 sizeof(queueheader_buf), "X-Claws-Encrypt:")) {
2126 param = atoi(&queueheader_buf[strlen("X-Claws-Encrypt:")]);
2127 use_encryption = param;
2129 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2130 sizeof(queueheader_buf), "X-Sylpheed-Encrypt:")) {
2131 param = atoi(&queueheader_buf[strlen("X-Sylpheed-Encrypt:")]);
2132 use_encryption = param;
2134 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2135 sizeof(queueheader_buf), "X-Claws-Privacy-System:")) {
2136 privacy_system = g_strdup(&queueheader_buf[strlen("X-Claws-Privacy-System:")]);
2138 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2139 sizeof(queueheader_buf), "X-Sylpheed-Privacy-System:")) {
2140 privacy_system = g_strdup(&queueheader_buf[strlen("X-Sylpheed-Privacy-System:")]);
2142 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2143 sizeof(queueheader_buf), "X-Priority: ")) {
2144 param = atoi(&queueheader_buf[strlen("X-Priority: ")]); /* mind the space */
2147 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2148 sizeof(queueheader_buf), "RMID:")) {
2149 gchar **tokens = g_strsplit(&queueheader_buf[strlen("RMID:")], "\t", 0);
2150 if (tokens[0] && tokens[1] && tokens[2]) {
2151 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2152 if (orig_item != NULL) {
2153 replyinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2158 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf,
2159 sizeof(queueheader_buf), "FMID:")) {
2160 gchar **tokens = g_strsplit(&queueheader_buf[strlen("FMID:")], "\t", 0);
2161 if (tokens[0] && tokens[1] && tokens[2]) {
2162 FolderItem *orig_item = folder_find_item_from_identifier(tokens[0]);
2163 if (orig_item != NULL) {
2164 fwdinfo = folder_item_get_msginfo_by_msgid(orig_item, tokens[2]);
2170 account = msginfo->folder->folder->account;
2173 if (!account && prefs_common.reedit_account_autosel) {
2174 gchar from[BUFFSIZE];
2175 if (!procheader_get_header_from_msginfo(msginfo, from, sizeof(from), "FROM:")) {
2176 extract_address(from);
2177 account = account_find_from_address(from, FALSE);
2181 account = cur_account;
2183 g_return_val_if_fail(account != NULL, NULL);
2185 compose = compose_create(account, msginfo->folder, COMPOSE_REEDIT, batch);
2187 compose->replyinfo = replyinfo;
2188 compose->fwdinfo = fwdinfo;
2190 compose->updating = TRUE;
2191 compose->priority = priority;
2193 if (privacy_system != NULL) {
2194 compose->privacy_system = privacy_system;
2195 compose_use_signing(compose, use_signing);
2196 compose_use_encryption(compose, use_encryption);
2197 compose_update_privacy_system_menu_item(compose, FALSE);
2199 activate_privacy_system(compose, account, FALSE);
2202 compose->targetinfo = procmsg_msginfo_copy(msginfo);
2204 compose_extract_original_charset(compose);
2206 if (folder_has_parent_of_type(msginfo->folder, F_QUEUE) ||
2207 folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
2208 gchar queueheader_buf[BUFFSIZE];
2210 /* Set message save folder */
2211 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "SCF:")) {
2214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), TRUE);
2215 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
2216 gtk_editable_insert_text(GTK_EDITABLE(compose->savemsg_entry), &queueheader_buf[4], strlen(&queueheader_buf[4]), &startpos);
2218 if (!procheader_get_header_from_msginfo(msginfo, queueheader_buf, sizeof(queueheader_buf), "RRCPT:")) {
2219 gint active = atoi(&queueheader_buf[strlen("RRCPT:")]);
2221 GtkItemFactory *ifactory;
2222 ifactory = gtk_item_factory_from_widget(compose->menubar);
2223 menu_set_active(ifactory, "/Options/Request Return Receipt", TRUE);
2228 if (compose_parse_header(compose, msginfo) < 0) {
2229 compose->updating = FALSE;
2230 compose_destroy(compose);
2233 compose_reedit_set_entry(compose, msginfo);
2235 textview = GTK_TEXT_VIEW(compose->text);
2236 textbuf = gtk_text_view_get_buffer(textview);
2237 compose_create_tags(textview, compose);
2239 mark = gtk_text_buffer_get_insert(textbuf);
2240 gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
2242 g_signal_handlers_block_by_func(G_OBJECT(textbuf),
2243 G_CALLBACK(compose_changed_cb),
2246 if (MSG_IS_ENCRYPTED(msginfo->flags)) {
2247 fp = procmime_get_first_encrypted_text_content(msginfo);
2249 compose_force_encryption(compose, account, TRUE);
2252 fp = procmime_get_first_text_content(msginfo);
2255 g_warning("Can't get text part\n");
2259 gboolean prev_autowrap = compose->autowrap;
2260 GtkTextBuffer *buffer = textbuf;
2262 while (fgets(buf, sizeof(buf), fp) != NULL) {
2264 gtk_text_buffer_insert(textbuf, &iter, buf, -1);
2270 compose_attach_parts(compose, msginfo);
2272 compose_colorize_signature(compose);
2274 g_signal_handlers_unblock_by_func(G_OBJECT(textbuf),
2275 G_CALLBACK(compose_changed_cb),
2278 gtk_widget_grab_focus(compose->text);
2280 if (prefs_common.auto_exteditor) {
2281 compose_exec_ext_editor(compose);
2283 compose->modified = FALSE;
2284 compose_set_title(compose);
2286 compose->updating = FALSE;
2287 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2288 SCROLL_TO_CURSOR(compose);
2290 if (compose->deferred_destroy) {
2291 compose_destroy(compose);
2295 compose->sig_str = compose_get_signature_str(compose);
2300 Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
2305 GtkItemFactory *ifactory;
2308 g_return_val_if_fail(msginfo != NULL, NULL);
2311 account = account_get_reply_account(msginfo,
2312 prefs_common.reply_account_autosel);
2313 g_return_val_if_fail(account != NULL, NULL);
2315 compose = compose_create(account, msginfo->folder, COMPOSE_REDIRECT, batch);
2317 compose->updating = TRUE;
2319 ifactory = gtk_item_factory_from_widget(compose->menubar);
2320 compose_create_tags(GTK_TEXT_VIEW(compose->text), compose);
2321 compose->replyinfo = NULL;
2322 compose->fwdinfo = NULL;
2324 compose_show_first_last_header(compose, TRUE);
2326 gtk_widget_grab_focus(compose->header_last->entry);
2328 filename = procmsg_get_message_file(msginfo);
2330 if (filename == NULL) {
2331 compose->updating = FALSE;
2332 compose_destroy(compose);
2337 compose->redirect_filename = filename;
2339 /* Set save folder */
2340 item = msginfo->folder;
2341 if (item && item->prefs && item->prefs->save_copy_to_folder) {
2342 gchar *folderidentifier;
2344 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
2345 folderidentifier = folder_item_get_identifier(item);
2346 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
2347 g_free(folderidentifier);
2350 compose_attach_parts(compose, msginfo);
2352 if (msginfo->subject)
2353 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry),
2355 gtk_editable_set_editable(GTK_EDITABLE(compose->subject_entry), FALSE);
2357 compose_quote_fmt(compose, msginfo, "%M", NULL, NULL, FALSE, FALSE,
2358 _("Message redirect format error at line %d."));
2359 quote_fmt_reset_vartable();
2360 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), FALSE);
2362 compose_colorize_signature(compose);
2364 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
2365 menu_set_sensitive(ifactory, "/Add...", FALSE);
2366 menu_set_sensitive(ifactory, "/Remove", FALSE);
2367 menu_set_sensitive(ifactory, "/Properties...", FALSE);
2369 ifactory = gtk_item_factory_from_widget(compose->menubar);
2370 menu_set_sensitive(ifactory, "/Message/Save", FALSE);
2371 menu_set_sensitive(ifactory, "/Message/Insert file", FALSE);
2372 menu_set_sensitive(ifactory, "/Message/Attach file", FALSE);
2373 menu_set_sensitive(ifactory, "/Message/Insert signature", FALSE);
2374 menu_set_sensitive(ifactory, "/Edit", FALSE);
2375 menu_set_sensitive(ifactory, "/Options", FALSE);
2376 menu_set_sensitive(ifactory, "/Tools/Show ruler", FALSE);
2377 menu_set_sensitive(ifactory, "/Tools/Actions", FALSE);
2379 if (compose->toolbar->draft_btn)
2380 gtk_widget_set_sensitive(compose->toolbar->draft_btn, FALSE);
2381 if (compose->toolbar->insert_btn)
2382 gtk_widget_set_sensitive(compose->toolbar->insert_btn, FALSE);
2383 if (compose->toolbar->attach_btn)
2384 gtk_widget_set_sensitive(compose->toolbar->attach_btn, FALSE);
2385 if (compose->toolbar->sig_btn)
2386 gtk_widget_set_sensitive(compose->toolbar->sig_btn, FALSE);
2387 if (compose->toolbar->exteditor_btn)
2388 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, FALSE);
2389 if (compose->toolbar->linewrap_current_btn)
2390 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, FALSE);
2391 if (compose->toolbar->linewrap_all_btn)
2392 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, FALSE);
2394 compose->modified = FALSE;
2395 compose_set_title(compose);
2396 compose->updating = FALSE;
2397 compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
2398 SCROLL_TO_CURSOR(compose);
2400 if (compose->deferred_destroy) {
2401 compose_destroy(compose);
2408 GList *compose_get_compose_list(void)
2410 return compose_list;
2413 void compose_entry_append(Compose *compose, const gchar *address,
2414 ComposeEntryType type)
2416 const gchar *header;
2418 gboolean in_quote = FALSE;
2419 if (!address || *address == '\0') return;
2426 header = N_("Bcc:");
2428 case COMPOSE_REPLYTO:
2429 header = N_("Reply-To:");
2431 case COMPOSE_NEWSGROUPS:
2432 header = N_("Newsgroups:");
2434 case COMPOSE_FOLLOWUPTO:
2435 header = N_( "Followup-To:");
2442 header = prefs_common_translated_header_name(header);
2444 cur = begin = (gchar *)address;
2446 /* we separate the line by commas, but not if we're inside a quoted
2448 while (*cur != '\0') {
2450 in_quote = !in_quote;
2451 if (*cur == ',' && !in_quote) {
2452 gchar *tmp = g_strdup(begin);
2454 tmp[cur-begin]='\0';
2457 while (*tmp == ' ' || *tmp == '\t')
2459 compose_add_header_entry(compose, header, tmp);
2466 gchar *tmp = g_strdup(begin);
2468 tmp[cur-begin]='\0';
2471 while (*tmp == ' ' || *tmp == '\t')
2473 compose_add_header_entry(compose, header, tmp);
2478 static void compose_entry_mark_default_to(Compose *compose, const gchar *mailto)
2480 static GdkColor yellow;
2481 static GdkColor black;
2482 static gboolean yellow_initialised = FALSE;
2486 if (!yellow_initialised) {
2487 gdk_color_parse("#f5f6be", &yellow);
2488 gdk_color_parse("#000000", &black);
2489 yellow_initialised = gdk_colormap_alloc_color(
2490 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
2491 yellow_initialised &= gdk_colormap_alloc_color(
2492 gdk_colormap_get_system(), &black, FALSE, TRUE);
2495 for (h_list = compose->header_list; h_list != NULL; h_list = h_list->next) {
2496 entry = GTK_ENTRY(((ComposeHeaderEntry *)h_list->data)->entry);
2497 if (gtk_entry_get_text(entry) &&
2498 !g_utf8_collate(gtk_entry_get_text(entry), mailto)) {
2499 if (yellow_initialised) {
2500 gtk_widget_modify_base(
2501 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2502 GTK_STATE_NORMAL, &yellow);
2503 gtk_widget_modify_text(
2504 GTK_WIDGET(((ComposeHeaderEntry *)h_list->data)->entry),
2505 GTK_STATE_NORMAL, &black);
2511 void compose_toolbar_cb(gint action, gpointer data)
2513 ToolbarItem *toolbar_item = (ToolbarItem*)data;
2514 Compose *compose = (Compose*)toolbar_item->parent;
2516 g_return_if_fail(compose != NULL);
2520 compose_send_cb(compose, 0, NULL);
2523 compose_send_later_cb(compose, 0, NULL);
2526 compose_draft_cb(compose, COMPOSE_QUIT_EDITING, NULL);
2529 compose_insert_file_cb(compose, 0, NULL);
2532 compose_attach_cb(compose, 0, NULL);
2535 compose_insert_sig(compose, FALSE);
2538 compose_ext_editor_cb(compose, 0, NULL);
2540 case A_LINEWRAP_CURRENT:
2541 compose_beautify_paragraph(compose, NULL, TRUE);
2543 case A_LINEWRAP_ALL:
2544 compose_wrap_all_full(compose, TRUE);
2547 compose_address_cb(compose, 0, NULL);
2550 case A_CHECK_SPELLING:
2551 compose_check_all(compose);
2559 static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
2564 gchar *subject = NULL;
2568 gchar **attach = NULL;
2570 /* get mailto parts but skip from */
2571 scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
2574 compose_entry_append(compose, to, to_type);
2576 compose_entry_append(compose, cc, COMPOSE_CC);
2578 compose_entry_append(compose, bcc, COMPOSE_BCC);
2580 if (!g_utf8_validate (subject, -1, NULL)) {
2581 temp = g_locale_to_utf8 (subject, -1, NULL, &len, NULL);
2582 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), temp);
2585 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
2589 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2590 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2593 gboolean prev_autowrap = compose->autowrap;
2595 compose->autowrap = FALSE;
2597 mark = gtk_text_buffer_get_insert(buffer);
2598 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2600 if (!g_utf8_validate (body, -1, NULL)) {
2601 temp = g_locale_to_utf8 (body, -1, NULL, &len, NULL);
2602 gtk_text_buffer_insert(buffer, &iter, temp, -1);
2605 gtk_text_buffer_insert(buffer, &iter, body, -1);
2607 gtk_text_buffer_insert(buffer, &iter, "\n", 1);
2609 compose->autowrap = prev_autowrap;
2610 if (compose->autowrap)
2611 compose_wrap_all(compose);
2615 gint i = 0, att = 0;
2616 gchar *warn_files = NULL;
2617 while (attach[i] != NULL) {
2618 gchar *utf8_filename = conv_filename_to_utf8(attach[i]);
2619 if (utf8_filename) {
2620 if (compose_attach_append(compose, attach[i], utf8_filename, NULL)) {
2621 gchar *tmp = g_strdup_printf("%s%s\n",
2622 warn_files?warn_files:"",
2628 g_free(utf8_filename);
2630 alertpanel_error(_("Couldn't attach a file (charset conversion failed)."));
2635 alertpanel_notice(ngettext(
2636 "The following file has been attached: \n%s",
2637 "The following files have been attached: \n%s", att), warn_files);
2649 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
2651 static HeaderEntry hentry[] = {{"Reply-To:", NULL, TRUE},
2652 {"Cc:", NULL, TRUE},
2653 {"References:", NULL, FALSE},
2654 {"Bcc:", NULL, TRUE},
2655 {"Newsgroups:", NULL, TRUE},
2656 {"Followup-To:", NULL, TRUE},
2657 {"List-Post:", NULL, FALSE},
2658 {"X-Priority:", NULL, FALSE},
2659 {NULL, NULL, FALSE}};
2675 g_return_val_if_fail(msginfo != NULL, -1);
2677 if ((fp = procmsg_open_message(msginfo)) == NULL) return -1;
2678 procheader_get_header_fields(fp, hentry);
2681 if (hentry[H_REPLY_TO].body != NULL) {
2682 if (hentry[H_REPLY_TO].body[0] != '\0') {
2684 conv_unmime_header(hentry[H_REPLY_TO].body,
2687 g_free(hentry[H_REPLY_TO].body);
2688 hentry[H_REPLY_TO].body = NULL;
2690 if (hentry[H_CC].body != NULL) {
2691 compose->cc = conv_unmime_header(hentry[H_CC].body, NULL);
2692 g_free(hentry[H_CC].body);
2693 hentry[H_CC].body = NULL;
2695 if (hentry[H_REFERENCES].body != NULL) {
2696 if (compose->mode == COMPOSE_REEDIT)
2697 compose->references = hentry[H_REFERENCES].body;
2699 compose->references = compose_parse_references
2700 (hentry[H_REFERENCES].body, msginfo->msgid);
2701 g_free(hentry[H_REFERENCES].body);
2703 hentry[H_REFERENCES].body = NULL;
2705 if (hentry[H_BCC].body != NULL) {
2706 if (compose->mode == COMPOSE_REEDIT)
2708 conv_unmime_header(hentry[H_BCC].body, NULL);
2709 g_free(hentry[H_BCC].body);
2710 hentry[H_BCC].body = NULL;
2712 if (hentry[H_NEWSGROUPS].body != NULL) {
2713 compose->newsgroups = hentry[H_NEWSGROUPS].body;
2714 hentry[H_NEWSGROUPS].body = NULL;
2716 if (hentry[H_FOLLOWUP_TO].body != NULL) {
2717 if (hentry[H_FOLLOWUP_TO].body[0] != '\0') {
2718 compose->followup_to =
2719 conv_unmime_header(hentry[H_FOLLOWUP_TO].body,
2722 g_free(hentry[H_FOLLOWUP_TO].body);
2723 hentry[H_FOLLOWUP_TO].body = NULL;
2725 if (hentry[H_LIST_POST].body != NULL) {
2728 extract_address(hentry[H_LIST_POST].body);
2729 if (hentry[H_LIST_POST].body[0] != '\0') {
2730 scan_mailto_url(hentry[H_LIST_POST].body,
2731 NULL, &to, NULL, NULL, NULL, NULL, NULL);
2733 g_free(compose->ml_post);
2734 compose->ml_post = to;
2737 g_free(hentry[H_LIST_POST].body);
2738 hentry[H_LIST_POST].body = NULL;
2741 /* CLAWS - X-Priority */
2742 if (compose->mode == COMPOSE_REEDIT)
2743 if (hentry[H_X_PRIORITY].body != NULL) {
2746 priority = atoi(hentry[H_X_PRIORITY].body);
2747 g_free(hentry[H_X_PRIORITY].body);
2749 hentry[H_X_PRIORITY].body = NULL;
2751 if (priority < PRIORITY_HIGHEST ||
2752 priority > PRIORITY_LOWEST)
2753 priority = PRIORITY_NORMAL;
2755 compose->priority = priority;
2758 if (compose->mode == COMPOSE_REEDIT) {
2759 if (msginfo->inreplyto && *msginfo->inreplyto)
2760 compose->inreplyto = g_strdup(msginfo->inreplyto);
2764 if (msginfo->msgid && *msginfo->msgid)
2765 compose->inreplyto = g_strdup(msginfo->msgid);
2767 if (!compose->references) {
2768 if (msginfo->msgid && *msginfo->msgid) {
2769 if (msginfo->inreplyto && *msginfo->inreplyto)
2770 compose->references =
2771 g_strdup_printf("<%s>\n\t<%s>",
2775 compose->references =
2776 g_strconcat("<", msginfo->msgid, ">",
2778 } else if (msginfo->inreplyto && *msginfo->inreplyto) {
2779 compose->references =
2780 g_strconcat("<", msginfo->inreplyto, ">",
2788 static gchar *compose_parse_references(const gchar *ref, const gchar *msgid)
2790 GSList *ref_id_list, *cur;
2794 ref_id_list = references_list_append(NULL, ref);
2795 if (!ref_id_list) return NULL;
2796 if (msgid && *msgid)
2797 ref_id_list = g_slist_append(ref_id_list, g_strdup(msgid));
2802 for (cur = ref_id_list; cur != NULL; cur = cur->next)
2803 /* "<" + Message-ID + ">" + CR+LF+TAB */
2804 len += strlen((gchar *)cur->data) + 5;
2806 if (len > MAX_REFERENCES_LEN) {
2807 /* remove second message-ID */
2808 if (ref_id_list && ref_id_list->next &&
2809 ref_id_list->next->next) {
2810 g_free(ref_id_list->next->data);
2811 ref_id_list = g_slist_remove
2812 (ref_id_list, ref_id_list->next->data);
2814 slist_free_strings(ref_id_list);
2815 g_slist_free(ref_id_list);
2822 new_ref = g_string_new("");
2823 for (cur = ref_id_list; cur != NULL; cur = cur->next) {
2824 if (new_ref->len > 0)
2825 g_string_append(new_ref, "\n\t");
2826 g_string_append_printf(new_ref, "<%s>", (gchar *)cur->data);
2829 slist_free_strings(ref_id_list);
2830 g_slist_free(ref_id_list);
2832 new_ref_str = new_ref->str;
2833 g_string_free(new_ref, FALSE);
2838 static gchar *compose_quote_fmt(Compose *compose, MsgInfo *msginfo,
2839 const gchar *fmt, const gchar *qmark,
2840 const gchar *body, gboolean rewrap,
2841 gboolean need_unescape,
2842 const gchar *err_msg)
2844 MsgInfo* dummyinfo = NULL;
2845 gchar *quote_str = NULL;
2847 gboolean prev_autowrap;
2848 const gchar *trimmed_body = body;
2849 gint cursor_pos = -1;
2850 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
2851 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
2856 SIGNAL_BLOCK(buffer);
2859 dummyinfo = compose_msginfo_new_from_compose(compose);
2860 msginfo = dummyinfo;
2863 if (qmark != NULL) {
2865 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
2866 compose->gtkaspell);
2868 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
2870 quote_fmt_scan_string(qmark);
2873 buf = quote_fmt_get_buffer();
2875 alertpanel_error(_("Quote mark format error."));
2877 Xstrdup_a(quote_str, buf, goto error)
2880 if (fmt && *fmt != '\0') {
2883 while (*trimmed_body == '\n')
2887 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE,
2888 compose->gtkaspell);
2890 quote_fmt_init(msginfo, quote_str, trimmed_body, FALSE, compose->account, FALSE);
2892 if (need_unescape) {
2895 /* decode \-escape sequences in the internal representation of the quote format */
2896 tmp = malloc(strlen(fmt)+1);
2897 pref_get_unescaped_pref(tmp, fmt);
2898 quote_fmt_scan_string(tmp);
2902 quote_fmt_scan_string(fmt);
2906 buf = quote_fmt_get_buffer();
2908 gint line = quote_fmt_get_line();
2909 alertpanel_error(err_msg, line);
2915 prev_autowrap = compose->autowrap;
2916 compose->autowrap = FALSE;
2918 mark = gtk_text_buffer_get_insert(buffer);
2919 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
2920 if (g_utf8_validate(buf, -1, NULL)) {
2921 gtk_text_buffer_insert(buffer, &iter, buf, -1);
2923 gchar *tmpout = NULL;
2924 tmpout = conv_codeset_strdup
2925 (buf, conv_get_locale_charset_str_no_utf8(),
2927 if (!tmpout || !g_utf8_validate(tmpout, -1, NULL)) {
2929 tmpout = g_malloc(strlen(buf)*2+1);
2930 conv_localetodisp(tmpout, strlen(buf)*2+1, buf);
2932 gtk_text_buffer_insert(buffer, &iter, tmpout, -1);
2936 cursor_pos = quote_fmt_get_cursor_pos();
2937 if (cursor_pos == -1)
2938 cursor_pos = gtk_text_iter_get_offset(&iter);
2939 compose->set_cursor_pos = cursor_pos;
2941 gtk_text_buffer_get_start_iter(buffer, &iter);
2942 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
2943 gtk_text_buffer_place_cursor(buffer, &iter);
2945 compose->autowrap = prev_autowrap;
2946 if (compose->autowrap && rewrap)
2947 compose_wrap_all(compose);
2954 SIGNAL_UNBLOCK(buffer);
2956 procmsg_msginfo_free( dummyinfo );
2961 /* if ml_post is of type addr@host and from is of type
2962 * addr-anything@host, return TRUE
2964 static gboolean is_subscription(const gchar *ml_post, const gchar *from)
2966 gchar *left_ml = NULL;
2967 gchar *right_ml = NULL;
2968 gchar *left_from = NULL;
2969 gchar *right_from = NULL;
2970 gboolean result = FALSE;
2972 if (!ml_post || !from)
2975 left_ml = g_strdup(ml_post);
2976 if (strstr(left_ml, "@")) {
2977 right_ml = strstr(left_ml, "@")+1;
2978 *(strstr(left_ml, "@")) = '\0';
2981 left_from = g_strdup(from);
2982 if (strstr(left_from, "@")) {
2983 right_from = strstr(left_from, "@")+1;
2984 *(strstr(left_from, "@")) = '\0';
2987 if (left_ml && left_from && right_ml && right_from
2988 && !strncmp(left_from, left_ml, strlen(left_ml))
2989 && !strcmp(right_from, right_ml)) {
2998 static gboolean same_address(const gchar *addr1, const gchar *addr2)
3000 gchar *my_addr1, *my_addr2;
3002 if (!addr1 || !addr2)
3005 Xstrdup_a(my_addr1, addr1, return FALSE);
3006 Xstrdup_a(my_addr2, addr2, return FALSE);
3008 extract_address(my_addr1);
3009 extract_address(my_addr2);
3011 return !strcasecmp(my_addr1, my_addr2);
3014 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
3015 gboolean to_all, gboolean to_ml,
3017 gboolean followup_and_reply_to)
3019 GSList *cc_list = NULL;
3022 gchar *replyto = NULL;
3023 GHashTable *to_table;
3025 gboolean reply_to_ml = FALSE;
3026 gboolean default_reply_to = FALSE;
3028 g_return_if_fail(compose->account != NULL);
3029 g_return_if_fail(msginfo != NULL);
3031 reply_to_ml = to_ml && compose->ml_post;
3033 default_reply_to = msginfo->folder &&
3034 msginfo->folder->prefs->enable_default_reply_to;
3036 if (compose->account->protocol != A_NNTP) {
3037 if (reply_to_ml && !default_reply_to) {
3039 gboolean is_subscr = is_subscription(compose->ml_post,
3042 /* normal answer to ml post with a reply-to */
3043 compose_entry_append(compose,
3046 if (compose->replyto
3047 && !same_address(compose->ml_post, compose->replyto))
3048 compose_entry_append(compose,
3052 /* answer to subscription confirmation */
3053 if (compose->replyto)
3054 compose_entry_append(compose,
3057 else if (msginfo->from)
3058 compose_entry_append(compose,
3063 else if (!(to_all || to_sender) && default_reply_to) {
3064 compose_entry_append(compose,
3065 msginfo->folder->prefs->default_reply_to,
3067 compose_entry_mark_default_to(compose,
3068 msginfo->folder->prefs->default_reply_to);
3073 Xstrdup_a(tmp1, msginfo->from, return);
3074 extract_address(tmp1);
3075 if (to_all || to_sender ||
3076 !account_find_from_address(tmp1, FALSE))
3077 compose_entry_append(compose,
3078 (compose->replyto && !to_sender)
3079 ? compose->replyto :
3080 msginfo->from ? msginfo->from : "",
3082 else if (!to_all && !to_sender) {
3083 if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
3084 !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
3085 !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
3086 if (compose->replyto) {
3087 compose_entry_append(compose,
3091 compose_entry_append(compose,
3092 msginfo->from ? msginfo->from : "",
3096 /* replying to own mail, use original recp */
3097 compose_entry_append(compose,
3098 msginfo->to ? msginfo->to : "",
3100 compose_entry_append(compose,
3101 msginfo->cc ? msginfo->cc : "",
3107 if (to_sender || (compose->followup_to &&
3108 !strncmp(compose->followup_to, "poster", 6)))
3109 compose_entry_append
3111 (compose->replyto ? compose->replyto :
3112 msginfo->from ? msginfo->from : ""),
3115 else if (followup_and_reply_to || to_all) {
3116 compose_entry_append
3118 (compose->replyto ? compose->replyto :
3119 msginfo->from ? msginfo->from : ""),
3122 compose_entry_append
3124 compose->followup_to ? compose->followup_to :
3125 compose->newsgroups ? compose->newsgroups : "",
3126 COMPOSE_NEWSGROUPS);
3129 compose_entry_append
3131 compose->followup_to ? compose->followup_to :
3132 compose->newsgroups ? compose->newsgroups : "",
3133 COMPOSE_NEWSGROUPS);
3136 if (msginfo->subject && *msginfo->subject) {
3140 buf = p = g_strdup(msginfo->subject);
3141 p += subject_get_prefix_length(p);
3142 memmove(buf, p, strlen(p) + 1);
3144 buf2 = g_strdup_printf("Re: %s", buf);
3145 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf2);
3150 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), "Re: ");
3152 if (to_ml && compose->ml_post) return;
3153 if (!to_all || compose->account->protocol == A_NNTP) return;
3155 if (compose->replyto) {
3156 Xstrdup_a(replyto, compose->replyto, return);
3157 extract_address(replyto);
3159 if (msginfo->from) {
3160 Xstrdup_a(from, msginfo->from, return);
3161 extract_address(from);
3164 if (replyto && from)
3165 cc_list = address_list_append_with_comments(cc_list, from);
3166 if (to_all && msginfo->folder &&
3167 msginfo->folder->prefs->enable_default_reply_to)
3168 cc_list = address_list_append_with_comments(cc_list,
3169 msginfo->folder->prefs->default_reply_to);
3170 cc_list = address_list_append_with_comments(cc_list, msginfo->to);
3171 cc_list = address_list_append_with_comments(cc_list, compose->cc);
3173 to_table = g_hash_table_new(g_str_hash, g_str_equal);
3175 g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
3176 if (compose->account) {
3177 g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
3178 GINT_TO_POINTER(1));
3180 /* remove address on To: and that of current account */
3181 for (cur = cc_list; cur != NULL; ) {
3182 GSList *next = cur->next;
3185 addr = g_utf8_strdown(cur->data, -1);
3186 extract_address(addr);
3188 if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
3189 cc_list = g_slist_remove(cc_list, cur->data);
3191 g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
3195 hash_free_strings(to_table);
3196 g_hash_table_destroy(to_table);
3199 for (cur = cc_list; cur != NULL; cur = cur->next)
3200 compose_entry_append(compose, (gchar *)cur->data,
3202 slist_free_strings(cc_list);
3203 g_slist_free(cc_list);
3208 #define SET_ENTRY(entry, str) \
3211 gtk_entry_set_text(GTK_ENTRY(compose->entry), str); \
3214 #define SET_ADDRESS(type, str) \
3217 compose_entry_append(compose, str, type); \
3220 static void compose_reedit_set_entry(Compose *compose, MsgInfo *msginfo)
3222 g_return_if_fail(msginfo != NULL);
3224 SET_ENTRY(subject_entry, msginfo->subject);
3225 SET_ENTRY(from_name, msginfo->from);
3226 SET_ADDRESS(COMPOSE_TO, msginfo->to);
3227 SET_ADDRESS(COMPOSE_CC, compose->cc);
3228 SET_ADDRESS(COMPOSE_BCC, compose->bcc);
3229 SET_ADDRESS(COMPOSE_REPLYTO, compose->replyto);
3230 SET_ADDRESS(COMPOSE_NEWSGROUPS, compose->newsgroups);
3231 SET_ADDRESS(COMPOSE_FOLLOWUPTO, compose->followup_to);
3233 compose_update_priority_menu_item(compose);
3234 compose_update_privacy_system_menu_item(compose, FALSE);
3235 compose_show_first_last_header(compose, TRUE);
3241 static void compose_insert_sig(Compose *compose, gboolean replace)
3243 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
3244 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
3246 GtkTextIter iter, iter_end;
3247 gint cur_pos, ins_pos;
3248 gboolean prev_autowrap;
3249 gboolean found = FALSE;
3250 gboolean exists = FALSE;
3252 g_return_if_fail(compose->account != NULL);
3256 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3257 G_CALLBACK(compose_changed_cb),
3260 mark = gtk_text_buffer_get_insert(buffer);
3261 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3262 cur_pos = gtk_text_iter_get_offset (&iter);
3265 gtk_text_buffer_get_end_iter(buffer, &iter);
3267 exists = (compose->sig_str != NULL);
3270 GtkTextIter first_iter, start_iter, end_iter;
3272 gtk_text_buffer_get_start_iter(buffer, &first_iter);
3274 if (!exists || compose->sig_str[0] == '\0')
3277 found = gtk_text_iter_forward_to_tag_toggle(&first_iter,
3278 compose->signature_tag);
3281 /* include previous \n\n */
3282 gtk_text_iter_backward_chars(&first_iter, 1);
3283 start_iter = first_iter;
3284 end_iter = first_iter;
3286 found = gtk_text_iter_forward_to_tag_toggle(&end_iter,
3287 compose->signature_tag);
3288 found &= gtk_text_iter_forward_to_tag_toggle(&end_iter,
3289 compose->signature_tag);
3291 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
3297 g_free(compose->sig_str);
3298 compose->sig_str = compose_get_signature_str(compose);
3300 cur_pos = gtk_text_iter_get_offset(&iter);
3302 if (!compose->sig_str || (replace && !compose->account->auto_sig)) {
3303 g_free(compose->sig_str);
3304 compose->sig_str = NULL;
3306 if (compose->sig_inserted == FALSE)
3307 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
3308 compose->sig_inserted = TRUE;
3310 cur_pos = gtk_text_iter_get_offset(&iter);
3311 gtk_text_buffer_insert(buffer, &iter, compose->sig_str, -1);
3313 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cur_pos);
3314 gtk_text_iter_forward_chars(&iter, 1);
3315 gtk_text_buffer_get_end_iter(buffer, &iter_end);
3316 gtk_text_buffer_apply_tag_by_name(buffer,"signature",&iter, &iter_end);
3318 if (cur_pos > gtk_text_buffer_get_char_count (buffer))
3319 cur_pos = gtk_text_buffer_get_char_count (buffer);
3322 /* put the cursor where it should be
3323 * either where the quote_fmt says, either where it was */
3324 if (compose->set_cursor_pos < 0)
3325 gtk_text_buffer_get_iter_at_offset(buffer, &iter, ins_pos);
3327 gtk_text_buffer_get_iter_at_offset(buffer, &iter,
3328 compose->set_cursor_pos);
3330 gtk_text_buffer_place_cursor(buffer, &iter);
3331 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3332 G_CALLBACK(compose_changed_cb),
3338 static gchar *compose_get_signature_str(Compose *compose)
3340 gchar *sig_body = NULL;
3341 gchar *sig_str = NULL;
3342 gchar *utf8_sig_str = NULL;
3344 g_return_val_if_fail(compose->account != NULL, NULL);
3346 if (!compose->account->sig_path)
3349 if (compose->account->sig_type == SIG_FILE) {
3350 if (!is_file_or_fifo_exist(compose->account->sig_path)) {
3351 g_warning("can't open signature file: %s\n",
3352 compose->account->sig_path);
3357 if (compose->account->sig_type == SIG_COMMAND)
3358 sig_body = get_command_output(compose->account->sig_path);
3362 tmp = file_read_to_str(compose->account->sig_path);
3365 sig_body = normalize_newlines(tmp);
3369 if (compose->account->sig_sep) {
3370 sig_str = g_strconcat("\n", compose->account->sig_sep, "\n", sig_body,
3374 sig_str = g_strconcat("\n", sig_body, NULL);
3377 if (g_utf8_validate(sig_str, -1, NULL) == TRUE)
3378 utf8_sig_str = sig_str;
3380 utf8_sig_str = conv_codeset_strdup
3381 (sig_str, conv_get_locale_charset_str_no_utf8(),
3387 return utf8_sig_str;
3390 static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *file)
3393 GtkTextBuffer *buffer;
3396 const gchar *cur_encoding;
3397 gchar buf[BUFFSIZE];
3400 gboolean prev_autowrap;
3401 gboolean badtxt = FALSE;
3403 g_return_val_if_fail(file != NULL, COMPOSE_INSERT_NO_FILE);
3405 if ((fp = g_fopen(file, "rb")) == NULL) {
3406 FILE_OP_ERROR(file, "fopen");
3407 return COMPOSE_INSERT_READ_ERROR;
3410 prev_autowrap = compose->autowrap;
3411 compose->autowrap = FALSE;
3413 text = GTK_TEXT_VIEW(compose->text);
3414 buffer = gtk_text_view_get_buffer(text);
3415 mark = gtk_text_buffer_get_insert(buffer);
3416 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
3418 g_signal_handlers_block_by_func(G_OBJECT(buffer),
3419 G_CALLBACK(text_inserted),
3422 cur_encoding = conv_get_locale_charset_str_no_utf8();
3424 while (fgets(buf, sizeof(buf), fp) != NULL) {
3427 if (g_utf8_validate(buf, -1, NULL) == TRUE)
3428 str = g_strdup(buf);
3430 str = conv_codeset_strdup
3431 (buf, cur_encoding, CS_INTERNAL);
3434 /* strip <CR> if DOS/Windows file,
3435 replace <CR> with <LF> if Macintosh file. */
3438 if (len > 0 && str[len - 1] != '\n') {
3440 if (str[len] == '\r') str[len] = '\n';
3443 gtk_text_buffer_insert(buffer, &iter, str, -1);
3447 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
3448 G_CALLBACK(text_inserted),
3450 compose->autowrap = prev_autowrap;
3451 if (compose->autowrap)
3452 compose_wrap_all(compose);
3457 return COMPOSE_INSERT_INVALID_CHARACTER;
3459 return COMPOSE_INSERT_SUCCESS;
3462 static gboolean compose_attach_append(Compose *compose, const gchar *file,
3463 const gchar *filename,
3464 const gchar *content_type)
3472 GtkListStore *store;
3474 gboolean has_binary = FALSE;
3476 if (!is_file_exist(file)) {
3477 gchar *file_from_uri = g_filename_from_uri(file, NULL, NULL);
3478 gboolean result = FALSE;
3479 if (file_from_uri && is_file_exist(file_from_uri)) {
3480 result = compose_attach_append(
3481 compose, file_from_uri,
3485 g_free(file_from_uri);
3488 alertpanel_error("File %s doesn't exist\n", filename);
3491 if ((size = get_file_size(file)) < 0) {
3492 alertpanel_error("Can't get file size of %s\n", filename);
3496 alertpanel_error(_("File %s is empty."), filename);
3499 if ((fp = g_fopen(file, "rb")) == NULL) {
3500 alertpanel_error(_("Can't read %s."), filename);
3505 ainfo = g_new0(AttachInfo, 1);
3506 auto_ainfo = g_auto_pointer_new_with_free
3507 (ainfo, (GFreeFunc) compose_attach_info_free);
3508 ainfo->file = g_strdup(file);
3511 ainfo->content_type = g_strdup(content_type);
3512 if (!g_ascii_strcasecmp(content_type, "message/rfc822")) {
3514 MsgFlags flags = {0, 0};
3516 if (procmime_get_encoding_for_text_file(file, &has_binary) == ENC_7BIT)
3517 ainfo->encoding = ENC_7BIT;
3519 ainfo->encoding = ENC_8BIT;
3521 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3522 if (msginfo && msginfo->subject)
3523 name = g_strdup(msginfo->subject);
3525 name = g_path_get_basename(filename ? filename : file);
3527 ainfo->name = g_strdup_printf(_("Message: %s"), name);
3529 procmsg_msginfo_free(msginfo);
3531 if (!g_ascii_strncasecmp(content_type, "text", 4))
3532 ainfo->encoding = procmime_get_encoding_for_text_file(file, &has_binary);
3534 ainfo->encoding = ENC_BASE64;
3535 name = g_path_get_basename(filename ? filename : file);
3536 ainfo->name = g_strdup(name);
3540 ainfo->content_type = procmime_get_mime_type(file);
3541 if (!ainfo->content_type) {
3542 ainfo->content_type =
3543 g_strdup("application/octet-stream");
3544 ainfo->encoding = ENC_BASE64;
3545 } else if (!g_ascii_strncasecmp(ainfo->content_type, "text", 4))
3547 procmime_get_encoding_for_text_file(file, &has_binary);
3549 ainfo->encoding = ENC_BASE64;
3550 name = g_path_get_basename(filename ? filename : file);
3551 ainfo->name = g_strdup(name);
3555 if (ainfo->name != NULL
3556 && !strcmp(ainfo->name, ".")) {
3557 g_free(ainfo->name);
3561 if (!strcmp(ainfo->content_type, "unknown") || has_binary) {
3562 g_free(ainfo->content_type);
3563 ainfo->content_type = g_strdup("application/octet-stream");
3566 ainfo->size = (goffset)size;
3567 size_text = to_human_readable((goffset)size);
3569 store = GTK_LIST_STORE(gtk_tree_view_get_model
3570 (GTK_TREE_VIEW(compose->attach_clist)));
3572 gtk_list_store_append(store, &iter);
3573 gtk_list_store_set(store, &iter,
3574 COL_MIMETYPE, ainfo->content_type,
3575 COL_SIZE, size_text,
3576 COL_NAME, ainfo->name,
3578 COL_AUTODATA, auto_ainfo,
3581 g_auto_pointer_free(auto_ainfo);
3582 compose_attach_update_label(compose);
3586 static void compose_use_signing(Compose *compose, gboolean use_signing)
3588 GtkItemFactory *ifactory;
3589 GtkWidget *menuitem = NULL;
3591 compose->use_signing = use_signing;
3592 ifactory = gtk_item_factory_from_widget(compose->menubar);
3593 menuitem = gtk_item_factory_get_item
3594 (ifactory, "/Options/Sign");
3595 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3599 static void compose_use_encryption(Compose *compose, gboolean use_encryption)
3601 GtkItemFactory *ifactory;
3602 GtkWidget *menuitem = NULL;
3604 compose->use_encryption = use_encryption;
3605 ifactory = gtk_item_factory_from_widget(compose->menubar);
3606 menuitem = gtk_item_factory_get_item
3607 (ifactory, "/Options/Encrypt");
3609 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
3613 #define NEXT_PART_NOT_CHILD(info) \
3615 node = info->node; \
3616 while (node->children) \
3617 node = g_node_last_child(node); \
3618 info = procmime_mimeinfo_next((MimeInfo *)node->data); \
3621 static void compose_attach_parts(Compose *compose, MsgInfo *msginfo)
3625 MimeInfo *firsttext = NULL;
3626 MimeInfo *encrypted = NULL;
3629 const gchar *partname = NULL;
3631 mimeinfo = procmime_scan_message(msginfo);
3632 if (!mimeinfo) return;
3634 if (mimeinfo->node->children == NULL) {
3635 procmime_mimeinfo_free_all(mimeinfo);
3639 /* find first content part */
3640 child = (MimeInfo *) mimeinfo->node->children->data;
3641 while (child && child->node->children && (child->type == MIMETYPE_MULTIPART))
3642 child = (MimeInfo *)child->node->children->data;
3644 if (child->type == MIMETYPE_TEXT) {
3646 debug_print("First text part found\n");
3647 } else if (compose->mode == COMPOSE_REEDIT &&
3648 child->type == MIMETYPE_APPLICATION &&
3649 !g_ascii_strcasecmp(child->subtype, "pgp-encrypted")) {
3650 encrypted = (MimeInfo *)child->node->parent->data;
3653 child = (MimeInfo *) mimeinfo->node->children->data;
3654 while (child != NULL) {
3657 if (child == encrypted) {
3658 /* skip this part of tree */
3659 NEXT_PART_NOT_CHILD(child);
3663 if (child->type == MIMETYPE_MULTIPART) {
3664 /* get the actual content */
3665 child = procmime_mimeinfo_next(child);
3669 if (child == firsttext) {
3670 child = procmime_mimeinfo_next(child);
3674 outfile = procmime_get_tmp_file_name(child);
3675 if ((err = procmime_get_part(outfile, child)) < 0)
3676 g_warning("Can't get the part of multipart message. (%s)", strerror(-err));
3678 gchar *content_type;
3680 content_type = procmime_get_content_type_str(child->type, child->subtype);
3682 /* if we meet a pgp signature, we don't attach it, but
3683 * we force signing. */
3684 if ((strcmp(content_type, "application/pgp-signature") &&
3685 strcmp(content_type, "application/pkcs7-signature") &&
3686 strcmp(content_type, "application/x-pkcs7-signature"))
3687 || compose->mode == COMPOSE_REDIRECT) {
3688 partname = procmime_mimeinfo_get_parameter(child, "filename");
3689 if (partname == NULL)
3690 partname = procmime_mimeinfo_get_parameter(child, "name");
3691 if (partname == NULL)
3693 compose_attach_append(compose, outfile,
3694 partname, content_type);
3696 compose_force_signing(compose, compose->account);
3698 g_free(content_type);
3701 NEXT_PART_NOT_CHILD(child);
3703 procmime_mimeinfo_free_all(mimeinfo);
3706 #undef NEXT_PART_NOT_CHILD
3711 WAIT_FOR_INDENT_CHAR,
3712 WAIT_FOR_INDENT_CHAR_OR_SPACE,
3715 /* return indent length, we allow:
3716 indent characters followed by indent characters or spaces/tabs,
3717 alphabets and numbers immediately followed by indent characters,
3718 and the repeating sequences of the above
3719 If quote ends with multiple spaces, only the first one is included. */
3720 static gchar *compose_get_quote_str(GtkTextBuffer *buffer,
3721 const GtkTextIter *start, gint *len)
3723 GtkTextIter iter = *start;
3727 IndentState state = WAIT_FOR_INDENT_CHAR;
3730 gint alnum_count = 0;
3731 gint space_count = 0;
3734 if (prefs_common.quote_chars == NULL) {
3738 while (!gtk_text_iter_ends_line(&iter)) {
3739 wc = gtk_text_iter_get_char(&iter);
3740 if (g_unichar_iswide(wc))
3742 clen = g_unichar_to_utf8(wc, ch);
3746 is_indent = strchr(prefs_common.quote_chars, ch[0]) ? TRUE : FALSE;
3747 is_space = g_unichar_isspace(wc);
3749 if (state == WAIT_FOR_INDENT_CHAR) {
3750 if (!is_indent && !g_unichar_isalnum(wc))
3753 quote_len += alnum_count + space_count + 1;
3754 alnum_count = space_count = 0;
3755 state = WAIT_FOR_INDENT_CHAR_OR_SPACE;
3758 } else if (state == WAIT_FOR_INDENT_CHAR_OR_SPACE) {
3759 if (!is_indent && !is_space && !g_unichar_isalnum(wc))
3763 else if (is_indent) {
3764 quote_len += alnum_count + space_count + 1;
3765 alnum_count = space_count = 0;
3768 state = WAIT_FOR_INDENT_CHAR;
3772 gtk_text_iter_forward_char(&iter);
3775 if (quote_len > 0 && space_count > 0)
3781 if (quote_len > 0) {
3783 gtk_text_iter_forward_chars(&iter, quote_len);
3784 return gtk_text_buffer_get_text(buffer, start, &iter, FALSE);
3790 /* return TRUE if the line is itemized */
3791 static gboolean compose_is_itemized(GtkTextBuffer *buffer,
3792 const GtkTextIter *start)
3794 GtkTextIter iter = *start;
3799 if (gtk_text_iter_ends_line(&iter))
3803 wc = gtk_text_iter_get_char(&iter);
3804 if (!g_unichar_isspace(wc))
3806 gtk_text_iter_forward_char(&iter);
3807 if (gtk_text_iter_ends_line(&iter))
3811 clen = g_unichar_to_utf8(wc, ch);
3815 if (!strchr("*-+", ch[0]))
3818 gtk_text_iter_forward_char(&iter);
3819 if (gtk_text_iter_ends_line(&iter))
3821 wc = gtk_text_iter_get_char(&iter);
3822 if (g_unichar_isspace(wc))
3828 static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
3829 const GtkTextIter *start,
3830 GtkTextIter *break_pos,
3834 GtkTextIter iter = *start, line_end = *start;
3835 PangoLogAttr *attrs;
3842 gboolean can_break = FALSE;
3843 gboolean do_break = FALSE;
3844 gboolean was_white = FALSE;
3845 gboolean prev_dont_break = FALSE;
3847 gtk_text_iter_forward_to_line_end(&line_end);
3848 str = gtk_text_buffer_get_text(buffer, &iter, &line_end, FALSE);
3849 len = g_utf8_strlen(str, -1);
3853 g_warning("compose_get_line_break_pos: len = 0!\n");
3857 /* g_print("breaking line: %d: %s (len = %d)\n",
3858 gtk_text_iter_get_line(&iter), str, len); */
3860 attrs = g_new(PangoLogAttr, len + 1);
3862 pango_default_break(str, -1, NULL, attrs, len + 1);
3866 /* skip quote and leading spaces */
3867 for (i = 0; *p != '\0' && i < len; i++) {
3870 wc = g_utf8_get_char(p);
3871 if (i >= quote_len && !g_unichar_isspace(wc))
3873 if (g_unichar_iswide(wc))
3875 else if (*p == '\t')
3879 p = g_utf8_next_char(p);
3882 for (; *p != '\0' && i < len; i++) {
3883 PangoLogAttr *attr = attrs + i;
3887 if (attr->is_line_break && can_break && was_white && !prev_dont_break)
3890 was_white = attr->is_white;
3892 /* don't wrap URI */
3893 if ((uri_len = get_uri_len(p)) > 0) {
3895 if (pos > 0 && col > max_col) {
3905 wc = g_utf8_get_char(p);
3906 if (g_unichar_iswide(wc)) {
3908 if (prev_dont_break && can_break && attr->is_line_break)
3910 } else if (*p == '\t')
3914 if (pos > 0 && col > max_col) {
3919 if (*p == '-' || *p == '/')
3920 prev_dont_break = TRUE;
3922 prev_dont_break = FALSE;
3924 p = g_utf8_next_char(p);
3928 debug_print("compose_get_line_break_pos(): do_break = %d, pos = %d, col = %d\n", do_break, pos, col);
3933 *break_pos = *start;
3934 gtk_text_iter_set_line_offset(break_pos, pos);
3939 static gboolean compose_join_next_line(Compose *compose,
3940 GtkTextBuffer *buffer,
3942 const gchar *quote_str)
3944 GtkTextIter iter_ = *iter, cur, prev, next, end;
3945 PangoLogAttr attrs[3];
3947 gchar *next_quote_str;
3950 gboolean keep_cursor = FALSE;
3952 if (!gtk_text_iter_forward_line(&iter_) ||
3953 gtk_text_iter_ends_line(&iter_)) {
3956 next_quote_str = compose_get_quote_str(buffer, &iter_, "e_len);
3958 if ((quote_str || next_quote_str) &&
3959 strcmp2(quote_str, next_quote_str) != 0) {
3960 g_free(next_quote_str);
3963 g_free(next_quote_str);
3966 if (quote_len > 0) {
3967 gtk_text_iter_forward_chars(&end, quote_len);
3968 if (gtk_text_iter_ends_line(&end)) {
3973 /* don't join itemized lines */
3974 if (compose_is_itemized(buffer, &end)) {
3978 /* don't join signature separator */
3979 if (compose_is_sig_separator(compose, buffer, &iter_)) {
3982 /* delete quote str */
3984 gtk_text_buffer_delete(buffer, &iter_, &end);
3986 /* don't join line breaks put by the user */
3988 gtk_text_iter_backward_char(&cur);
3989 if (gtk_text_iter_has_tag(&cur, compose->no_join_tag)) {
3990 gtk_text_iter_forward_char(&cur);
3994 gtk_text_iter_forward_char(&cur);
3995 /* delete linebreak and extra spaces */
3996 while (gtk_text_iter_backward_char(&cur)) {
3997 wc1 = gtk_text_iter_get_char(&cur);
3998 if (!g_unichar_isspace(wc1))
4003 while (!gtk_text_iter_ends_line(&cur)) {
4004 wc1 = gtk_text_iter_get_char(&cur);
4005 if (!g_unichar_isspace(wc1))
4007 gtk_text_iter_forward_char(&cur);
4010 if (!gtk_text_iter_equal(&prev, &next)) {
4013 mark = gtk_text_buffer_get_insert(buffer);
4014 gtk_text_buffer_get_iter_at_mark(buffer, &cur, mark);
4015 if (gtk_text_iter_equal(&prev, &cur))
4017 gtk_text_buffer_delete(buffer, &prev, &next);
4021 /* insert space if required */
4022 gtk_text_iter_backward_char(&prev);
4023 wc1 = gtk_text_iter_get_char(&prev);
4024 wc2 = gtk_text_iter_get_char(&next);
4025 gtk_text_iter_forward_char(&next);
4026 str = gtk_text_buffer_get_text(buffer, &prev, &next, FALSE);
4027 pango_default_break(str, -1, NULL, attrs, 3);
4028 if (!attrs[1].is_line_break ||
4029 (!g_unichar_iswide(wc1) || !g_unichar_iswide(wc2))) {
4030 gtk_text_buffer_insert(buffer, &iter_, " ", 1);
4032 gtk_text_iter_backward_char(&iter_);
4033 gtk_text_buffer_place_cursor(buffer, &iter_);
4042 #define ADD_TXT_POS(bp_, ep_, pti_) \
4043 if ((last->next = alloca(sizeof(struct txtpos))) != NULL) { \
4044 last = last->next; \
4045 last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
4046 last->next = NULL; \
4048 g_warning("alloc error scanning URIs\n"); \
4051 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
4053 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4054 GtkTextBuffer *buffer;
4055 GtkTextIter iter, break_pos, end_of_line;
4056 gchar *quote_str = NULL;
4058 gboolean wrap_quote = prefs_common.linewrap_quote;
4059 gboolean prev_autowrap = compose->autowrap;
4060 gint startq_offset = -1, noq_offset = -1;
4061 gint uri_start = -1, uri_stop = -1;
4062 gint nouri_start = -1, nouri_stop = -1;
4063 gint num_blocks = 0;
4064 gint quotelevel = -1;
4065 gboolean modified = force;
4066 gboolean removed = FALSE;
4067 gboolean modified_before_remove = FALSE;
4069 gboolean start = TRUE;
4074 if (compose->draft_timeout_tag == -2) {
4078 compose->autowrap = FALSE;
4080 buffer = gtk_text_view_get_buffer(text);
4081 undo_wrapping(compose->undostruct, TRUE);
4086 mark = gtk_text_buffer_get_insert(buffer);
4087 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
4091 if (compose->draft_timeout_tag == -2) {
4092 if (gtk_text_iter_ends_line(&iter)) {
4093 while (gtk_text_iter_ends_line(&iter) &&
4094 gtk_text_iter_forward_line(&iter))
4097 while (gtk_text_iter_backward_line(&iter)) {
4098 if (gtk_text_iter_ends_line(&iter)) {
4099 gtk_text_iter_forward_line(&iter);
4105 /* move to line start */
4106 gtk_text_iter_set_line_offset(&iter, 0);
4108 /* go until paragraph end (empty line) */
4109 while (start || !gtk_text_iter_ends_line(&iter)) {
4110 gchar *scanpos = NULL;
4111 /* parse table - in order of priority */
4113 const gchar *needle; /* token */
4115 /* token search function */
4116 gchar *(*search) (const gchar *haystack,
4117 const gchar *needle);
4118 /* part parsing function */
4119 gboolean (*parse) (const gchar *start,
4120 const gchar *scanpos,
4124 /* part to URI function */
4125 gchar *(*build_uri) (const gchar *bp,
4129 static struct table parser[] = {
4130 {"http://", strcasestr, get_uri_part, make_uri_string},
4131 {"https://", strcasestr, get_uri_part, make_uri_string},
4132 {"ftp://", strcasestr, get_uri_part, make_uri_string},
4133 {"sftp://", strcasestr, get_uri_part, make_uri_string},
4134 {"www.", strcasestr, get_uri_part, make_http_string},
4135 {"mailto:", strcasestr, get_uri_part, make_uri_string},
4136 {"@", strcasestr, get_email_part, make_email_string}
4138 const gint PARSE_ELEMS = sizeof parser / sizeof parser[0];
4139 gint last_index = PARSE_ELEMS;
4141 gchar *o_walk = NULL, *walk = NULL, *bp = NULL, *ep = NULL;
4145 if (!prev_autowrap && num_blocks == 0) {
4147 g_signal_handlers_block_by_func(G_OBJECT(buffer),
4148 G_CALLBACK(text_inserted),
4151 if (gtk_text_iter_has_tag(&iter, compose->no_wrap_tag) && !force)
4154 uri_start = uri_stop = -1;
4156 quote_str = compose_get_quote_str(buffer, &iter, "e_len);
4159 debug_print("compose_beautify_paragraph(): quote_str = '%s'\n", quote_str);
4160 if (startq_offset == -1)
4161 startq_offset = gtk_text_iter_get_offset(&iter);
4162 quotelevel = get_quote_level(quote_str, prefs_common.quote_chars);
4163 if (quotelevel > 2) {
4164 /* recycle colors */
4165 if (prefs_common.recycle_quote_colors)
4174 if (startq_offset == -1)
4175 noq_offset = gtk_text_iter_get_offset(&iter);
4179 if (prev_autowrap == FALSE && !force && !wrap_quote) {
4182 if (gtk_text_iter_ends_line(&iter)) {
4184 } else if (compose_get_line_break_pos(buffer, &iter, &break_pos,
4185 prefs_common.linewrap_len,
4187 GtkTextIter prev, next, cur;
4189 if (prev_autowrap != FALSE || force) {
4190 compose->automatic_break = TRUE;
4192 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4193 compose->automatic_break = FALSE;
4194 } else if (quote_str && wrap_quote) {
4195 compose->automatic_break = TRUE;
4197 gtk_text_buffer_insert(buffer, &break_pos, "\n", 1);
4198 compose->automatic_break = FALSE;
4201 /* remove trailing spaces */
4203 gtk_text_iter_backward_char(&cur);
4205 while (!gtk_text_iter_starts_line(&cur)) {
4208 gtk_text_iter_backward_char(&cur);
4209 wc = gtk_text_iter_get_char(&cur);
4210 if (!g_unichar_isspace(wc))
4214 if (!gtk_text_iter_equal(&prev, &next)) {
4215 gtk_text_buffer_delete(buffer, &prev, &next);
4217 gtk_text_iter_forward_char(&break_pos);
4221 gtk_text_buffer_insert(buffer, &break_pos,
4225 modified |= compose_join_next_line(compose, buffer, &iter, quote_str);
4227 /* move iter to current line start */
4228 gtk_text_iter_set_line_offset(&iter, 0);
4235 /* move iter to next line start */
4241 if (!prev_autowrap && num_blocks > 0) {
4243 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
4244 G_CALLBACK(text_inserted),
4248 while (!gtk_text_iter_ends_line(&end_of_line)) {
4249 gtk_text_iter_forward_char(&end_of_line);
4251 o_walk = walk = gtk_text_buffer_get_text(buffer, &iter, &end_of_line, FALSE);
4253 nouri_start = gtk_text_iter_get_offset(&iter);
4254 nouri_stop = gtk_text_iter_get_offset(&end_of_line);
4256 walk_pos = gtk_text_iter_get_offset(&iter);
4257 /* FIXME: this looks phony. scanning for anything in the parse table */
4258 for (n = 0; n < PARSE_ELEMS; n++) {
4261 tmp = parser[n].search(walk, parser[n].needle);
4263 if (scanpos == NULL || tmp < scanpos) {
4272 /* check if URI can be parsed */
4273 if (parser[last_index].parse(walk, scanpos, (const gchar **)&bp,
4274 (const gchar **)&ep, FALSE)
4275 && (size_t) (ep - bp - 1) > strlen(parser[last_index].needle)) {
4279 strlen(parser[last_index].needle);
4282 uri_start = walk_pos + (bp - o_walk);
4283 uri_stop = walk_pos + (ep - o_walk);
4287 gtk_text_iter_forward_line(&iter);
4290 if (startq_offset != -1) {
4291 GtkTextIter startquote, endquote;
4292 gtk_text_buffer_get_iter_at_offset(
4293 buffer, &startquote, startq_offset);
4296 switch (quotelevel) {
4298 if (!gtk_text_iter_has_tag(&startquote, compose->quote0_tag) ||
4299 !gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) {
4300 gtk_text_buffer_apply_tag_by_name(
4301 buffer, "quote0", &startquote, &endquote);
4302 gtk_text_buffer_remove_tag_by_name(
4303 buffer, "quote1", &startquote, &endquote);
4304 gtk_text_buffer_remove_tag_by_name(
4305 buffer, "quote2", &startquote, &endquote);
4310 if (!gtk_text_iter_has_tag(&startquote, compose->quote1_tag) ||
4311 !gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) {
4312 gtk_text_buffer_apply_tag_by_name(
4313 buffer, "quote1", &startquote, &endquote);
4314 gtk_text_buffer_remove_tag_by_name(
4315 buffer, "quote0", &startquote, &endquote);
4316 gtk_text_buffer_remove_tag_by_name(
4317 buffer, "quote2", &startquote, &endquote);
4322 if (!gtk_text_iter_has_tag(&startquote, compose->quote2_tag) ||
4323 !gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag)) {
4324 gtk_text_buffer_apply_tag_by_name(
4325 buffer, "quote2", &startquote, &endquote);
4326 gtk_text_buffer_remove_tag_by_name(
4327 buffer, "quote0", &startquote, &endquote);
4328 gtk_text_buffer_remove_tag_by_name(
4329 buffer, "quote1", &startquote, &endquote);
4335 } else if (noq_offset != -1) {
4336 GtkTextIter startnoquote, endnoquote;
4337 gtk_text_buffer_get_iter_at_offset(
4338 buffer, &startnoquote, noq_offset);
4341 if ((gtk_text_iter_has_tag(&startnoquote, compose->quote0_tag)
4342 && gtk_text_iter_has_tag(&end_of_line, compose->quote0_tag)) ||
4343 (gtk_text_iter_has_tag(&startnoquote, compose->quote1_tag)
4344 && gtk_text_iter_has_tag(&end_of_line, compose->quote1_tag)) ||
4345 (gtk_text_iter_has_tag(&startnoquote, compose->quote2_tag)
4346 && gtk_text_iter_has_tag(&end_of_line, compose->quote2_tag))) {
4347 gtk_text_buffer_remove_tag_by_name(
4348 buffer, "quote0", &startnoquote, &endnoquote);
4349 gtk_text_buffer_remove_tag_by_name(
4350 buffer, "quote1", &startnoquote, &endnoquote);
4351 gtk_text_buffer_remove_tag_by_name(
4352 buffer, "quote2", &startnoquote, &endnoquote);
4358 if (uri_start != nouri_start && uri_stop != nouri_stop) {
4359 GtkTextIter nouri_start_iter, nouri_end_iter;
4360 gtk_text_buffer_get_iter_at_offset(
4361 buffer, &nouri_start_iter, nouri_start);
4362 gtk_text_buffer_get_iter_at_offset(
4363 buffer, &nouri_end_iter, nouri_stop);
4364 if (gtk_text_iter_has_tag(&nouri_start_iter, compose->uri_tag) &&
4365 gtk_text_iter_has_tag(&nouri_end_iter, compose->uri_tag)) {
4366 gtk_text_buffer_remove_tag_by_name(
4367 buffer, "link", &nouri_start_iter, &nouri_end_iter);
4368 modified_before_remove = modified;
4373 if (uri_start >= 0 && uri_stop > 0) {
4374 GtkTextIter uri_start_iter, uri_end_iter, back;
4375 gtk_text_buffer_get_iter_at_offset(
4376 buffer, &uri_start_iter, uri_start);
4377 gtk_text_buffer_get_iter_at_offset(
4378 buffer, &uri_end_iter, uri_stop);
4379 back = uri_end_iter;
4380 gtk_text_iter_backward_char(&back);
4381 if (!gtk_text_iter_has_tag(&uri_start_iter, compose->uri_tag) ||
4382 !gtk_text_iter_has_tag(&back, compose->uri_tag)) {
4383 gtk_text_buffer_apply_tag_by_name(
4384 buffer, "link", &uri_start_iter, &uri_end_iter);
4386 if (removed && !modified_before_remove) {
4392 debug_print("not modified, out after %d lines\n", lines);
4397 debug_print("modified, out after %d lines\n", lines);
4401 undo_wrapping(compose->undostruct, FALSE);
4402 compose->autowrap = prev_autowrap;
4407 void compose_action_cb(void *data)
4409 Compose *compose = (Compose *)data;
4410 compose_wrap_all(compose);
4413 static void compose_wrap_all(Compose *compose)
4415 compose_wrap_all_full(compose, FALSE);
4418 static void compose_wrap_all_full(Compose *compose, gboolean force)
4420 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
4421 GtkTextBuffer *buffer;
4423 gboolean modified = TRUE;
4425 buffer = gtk_text_view_get_buffer(text);
4427 gtk_text_buffer_get_start_iter(buffer, &iter);
4428 while (!gtk_text_iter_is_end(&iter) && modified)
4429 modified = compose_beautify_paragraph(compose, &iter, force);
4433 static void compose_set_title(Compose *compose)
4439 edited = compose->modified ? _(" [Edited]") : "";
4441 subject = gtk_editable_get_chars(
4442 GTK_EDITABLE(compose->subject_entry), 0, -1);
4444 #ifndef GENERIC_UMPC
4445 if (subject && strlen(subject))
4446 str = g_strdup_printf(_("%s - Compose message%s"),
4449 str = g_strdup_printf(_("[no subject] - Compose message%s"), edited);
4451 str = g_strdup(_("Compose message"));
4454 gtk_window_set_title(GTK_WINDOW(compose->window), str);
4460 * compose_current_mail_account:
4462 * Find a current mail account (the currently selected account, or the
4463 * default account, if a news account is currently selected). If a
4464 * mail account cannot be found, display an error message.
4466 * Return value: Mail account, or NULL if not found.
4468 static PrefsAccount *
4469 compose_current_mail_account(void)
4473 if (cur_account && cur_account->protocol != A_NNTP)
4476 ac = account_get_default();
4477 if (!ac || ac->protocol == A_NNTP) {
4478 alertpanel_error(_("Account for sending mail is not specified.\n"
4479 "Please select a mail account before sending."));
4486 #define QUOTE_IF_REQUIRED(out, str) \
4488 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4492 len = strlen(str) + 3; \
4493 if ((__tmp = alloca(len)) == NULL) { \
4494 g_warning("can't allocate memory\n"); \
4495 g_string_free(header, TRUE); \
4498 g_snprintf(__tmp, len, "\"%s\"", str); \
4503 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4504 g_warning("can't allocate memory\n"); \
4505 g_string_free(header, TRUE); \
4508 strcpy(__tmp, str); \
4514 #define QUOTE_IF_REQUIRED_NORMAL(out, str, errret) \
4516 if (*str != '"' && strpbrk(str, ",.[]<>")) { \
4520 len = strlen(str) + 3; \
4521 if ((__tmp = alloca(len)) == NULL) { \
4522 g_warning("can't allocate memory\n"); \
4525 g_snprintf(__tmp, len, "\"%s\"", str); \
4530 if ((__tmp = alloca(strlen(str) + 1)) == NULL) { \
4531 g_warning("can't allocate memory\n"); \
4534 strcpy(__tmp, str); \
4540 static void compose_select_account(Compose *compose, PrefsAccount *account,
4543 GtkItemFactory *ifactory;
4546 g_return_if_fail(account != NULL);
4548 compose->account = account;
4550 if (account->name && *account->name) {
4552 QUOTE_IF_REQUIRED_NORMAL(buf, account->name, return);
4553 from = g_strdup_printf("%s <%s>",
4554 buf, account->address);
4555 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4557 from = g_strdup_printf("<%s>",
4559 gtk_entry_set_text(GTK_ENTRY(compose->from_name), from);
4564 compose_set_title(compose);
4566 ifactory = gtk_item_factory_from_widget(compose->menubar);
4568 if (account->default_sign && compose->mode != COMPOSE_REDIRECT)
4569 menu_set_active(ifactory, "/Options/Sign", TRUE);
4571 menu_set_active(ifactory, "/Options/Sign", FALSE);
4572 if (account->default_encrypt && compose->mode != COMPOSE_REDIRECT)
4573 menu_set_active(ifactory, "/Options/Encrypt", TRUE);
4575 menu_set_active(ifactory, "/Options/Encrypt", FALSE);
4577 activate_privacy_system(compose, account, FALSE);
4579 if (!init && compose->mode != COMPOSE_REDIRECT) {
4580 undo_block(compose->undostruct);
4581 compose_insert_sig(compose, TRUE);
4582 undo_unblock(compose->undostruct);
4586 /* use account's dict info if set */
4587 if (compose->gtkaspell) {
4588 if (account->enable_default_dictionary)
4589 gtkaspell_change_dict(compose->gtkaspell,
4590 account->default_dictionary, FALSE);
4591 if (account->enable_default_alt_dictionary)
4592 gtkaspell_change_alt_dict(compose->gtkaspell,
4593 account->default_alt_dictionary);
4594 if (account->enable_default_dictionary
4595 || account->enable_default_alt_dictionary)
4596 compose_spell_menu_changed(compose);
4601 gboolean compose_check_for_valid_recipient(Compose *compose) {
4602 gchar *recipient_headers_mail[] = {"To:", "Cc:", "Bcc:", NULL};
4603 gchar *recipient_headers_news[] = {"Newsgroups:", NULL};
4604 gboolean recipient_found = FALSE;
4608 /* free to and newsgroup list */
4609 slist_free_strings(compose->to_list);
4610 g_slist_free(compose->to_list);
4611 compose->to_list = NULL;
4613 slist_free_strings(compose->newsgroup_list);
4614 g_slist_free(compose->newsgroup_list);
4615 compose->newsgroup_list = NULL;
4617 /* search header entries for to and newsgroup entries */
4618 for (list = compose->header_list; list; list = list->next) {
4621 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4622 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4625 if (entry[0] != '\0') {
4626 for (strptr = recipient_headers_mail; *strptr != NULL; strptr++) {
4627 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4628 compose->to_list = address_list_append(compose->to_list, entry);
4629 recipient_found = TRUE;
4632 for (strptr = recipient_headers_news; *strptr != NULL; strptr++) {
4633 if (!strcmp(header, prefs_common_translated_header_name(*strptr))) {
4634 compose->newsgroup_list = newsgroup_list_append(compose->newsgroup_list, entry);
4635 recipient_found = TRUE;
4642 return recipient_found;
4645 static gboolean compose_check_for_set_recipients(Compose *compose)
4647 if (compose->account->set_autocc && compose->account->auto_cc) {
4648 gboolean found_other = FALSE;
4650 /* search header entries for to and newsgroup entries */
4651 for (list = compose->header_list; list; list = list->next) {
4654 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4655 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4658 if (strcmp(entry, compose->account->auto_cc)
4659 || strcmp(header, prefs_common_translated_header_name("Cc:"))) {
4669 if (compose->batch) {
4670 gtk_widget_show_all(compose->window);
4672 aval = alertpanel(_("Send"),
4673 _("The only recipient is the default CC address. Send anyway?"),
4674 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4675 if (aval != G_ALERTALTERNATE)
4679 if (compose->account->set_autobcc && compose->account->auto_bcc) {
4680 gboolean found_other = FALSE;
4682 /* search header entries for to and newsgroup entries */
4683 for (list = compose->header_list; list; list = list->next) {
4686 entry = gtk_editable_get_chars(GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
4687 header = gtk_editable_get_chars(GTK_EDITABLE(GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
4690 if (strcmp(entry, compose->account->auto_bcc)
4691 || strcmp(header, prefs_common_translated_header_name("Bcc:"))) {
4701 if (compose->batch) {
4702 gtk_widget_show_all(compose->window);
4704 aval = alertpanel(_("Send"),
4705 _("The only recipient is the default BCC address. Send anyway?"),
4706 GTK_STOCK_CANCEL, _("+_Send"), NULL);
4707 if (aval != G_ALERTALTERNATE)
4714 static gboolean compose_check_entries(Compose *compose, gboolean check_everything)
4718 if (compose_check_for_valid_recipient(compose) == FALSE) {
4719 if (compose->batch) {
4720 gtk_widget_show_all(compose->window);
4722 alertpanel_error(_("Recipient is not specified."));
4726 if (compose_check_for_set_recipients(compose) == FALSE) {
4730 if (!compose->batch) {
4731 str = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
4732 if (*str == '\0' && check_everything == TRUE &&
4733 compose->mode != COMPOSE_REDIRECT) {
4735 gchar *button_label;
4738 if (compose->sending)
4739 button_label = _("+_Send");
4741 button_label = _("+_Queue");
4742 message = g_strdup_printf(_("Subject is empty. %s"),
4743 compose->sending?_("Send it anyway?"):
4744 _("Queue it anyway?"));
4746 aval = alertpanel(compose->sending?_("Send"):_("Send later"), message,
4747 GTK_STOCK_CANCEL, button_label, NULL);
4749 if (aval != G_ALERTALTERNATE)
4754 if (check_everything && hooks_invoke(COMPOSE_CHECK_BEFORE_SEND_HOOKLIST, compose))
4760 gint compose_send(Compose *compose)
4763 FolderItem *folder = NULL;
4765 gchar *msgpath = NULL;
4766 gboolean discard_window = FALSE;
4767 gchar *errstr = NULL;
4768 gchar *tmsgid = NULL;
4769 MainWindow *mainwin = mainwindow_get_mainwindow();
4770 gboolean queued_removed = FALSE;
4772 if (prefs_common.send_dialog_invisible
4773 || compose->batch == TRUE)
4774 discard_window = TRUE;
4776 compose_allow_user_actions (compose, FALSE);
4777 compose->sending = TRUE;
4779 if (compose_check_entries(compose, TRUE) == FALSE) {
4780 if (compose->batch) {
4781 gtk_widget_show_all(compose->window);
4787 val = compose_queue(compose, &msgnum, &folder, &msgpath, TRUE);
4790 if (compose->batch) {
4791 gtk_widget_show_all(compose->window);
4794 alertpanel_error(_("Could not queue message for sending:\n\n"
4795 "Charset conversion failed."));
4796 } else if (val == -5) {
4797 alertpanel_error(_("Could not queue message for sending:\n\n"
4798 "Couldn't get recipient encryption key."));
4799 } else if (val == -6) {
4801 } else if (val == -3) {
4802 if (privacy_peek_error())
4803 alertpanel_error(_("Could not queue message for sending:\n\n"
4804 "Signature failed: %s"), privacy_get_error());
4805 } else if (val == -2 && errno != 0) {
4806 alertpanel_error(_("Could not queue message for sending:\n\n%s."), strerror(errno));
4808 alertpanel_error(_("Could not queue message for sending."));
4813 tmsgid = compose->msgid ? g_strdup(compose->msgid) : NULL;
4814 if (discard_window) {
4815 compose->sending = FALSE;
4816 compose_close(compose);
4817 /* No more compose access in the normal codepath
4818 * after this point! */
4823 alertpanel_error(_("The message was queued but could not be "
4824 "sent.\nUse \"Send queued messages\" from "
4825 "the main window to retry."));
4826 if (!discard_window) {
4833 if (msgpath == NULL) {
4834 msgpath = folder_item_fetch_msg(folder, msgnum);
4835 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4838 val = procmsg_send_message_queue(msgpath, &errstr, folder, msgnum, &queued_removed);
4839 claws_unlink(msgpath);
4842 if (!discard_window) {
4844 if (!queued_removed)
4845 folder_item_remove_msg(folder, msgnum);
4846 folder_item_scan(folder);
4848 /* make sure we delete that */
4849 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4851 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4852 folder_item_remove_msg(folder, tmp->msgnum);
4853 procmsg_msginfo_free(tmp);
4860 if (!queued_removed)
4861 folder_item_remove_msg(folder, msgnum);
4862 folder_item_scan(folder);
4864 /* make sure we delete that */
4865 MsgInfo *tmp = folder_item_get_msginfo_by_msgid(folder, tmsgid);
4867 debug_print("removing %d via %s\n", tmp->msgnum, tmsgid);
4868 folder_item_remove_msg(folder, tmp->msgnum);
4869 procmsg_msginfo_free(tmp);
4872 if (!discard_window) {
4873 compose->sending = FALSE;
4874 compose_allow_user_actions (compose, TRUE);
4875 compose_close(compose);
4879 alertpanel_error_log(_("%s\nUse \"Send queued messages\" from "
4880 "the main window to retry."), errstr);
4883 alertpanel_error_log(_("The message was queued but could not be "
4884 "sent.\nUse \"Send queued messages\" from "
4885 "the main window to retry."));
4887 if (!discard_window) {
4896 toolbar_main_set_sensitive(mainwin);
4897 main_window_set_menu_sensitive(mainwin);
4903 compose_allow_user_actions (compose, TRUE);
4904 compose->sending = FALSE;
4905 compose->modified = TRUE;
4906 toolbar_main_set_sensitive(mainwin);
4907 main_window_set_menu_sensitive(mainwin);
4912 static gboolean compose_use_attach(Compose *compose)
4914 GtkTreeModel *model = gtk_tree_view_get_model
4915 (GTK_TREE_VIEW(compose->attach_clist));
4916 return gtk_tree_model_iter_n_children(model, NULL) > 0;
4919 static gint compose_redirect_write_headers_from_headerlist(Compose *compose,
4922 gchar buf[BUFFSIZE];
4924 gboolean first_to_address;
4925 gboolean first_cc_address;
4927 ComposeHeaderEntry *headerentry;
4928 const gchar *headerentryname;
4929 const gchar *cc_hdr;
4930 const gchar *to_hdr;
4931 gboolean err = FALSE;
4933 debug_print("Writing redirect header\n");
4935 cc_hdr = prefs_common_translated_header_name("Cc:");
4936 to_hdr = prefs_common_translated_header_name("To:");
4938 first_to_address = TRUE;
4939 for (list = compose->header_list; list; list = list->next) {
4940 headerentry = ((ComposeHeaderEntry *)list->data);
4941 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4943 if (g_utf8_collate(headerentryname, to_hdr) == 0) {
4944 const gchar *entstr = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4945 Xstrdup_a(str, entstr, return -1);
4947 if (str[0] != '\0') {
4948 compose_convert_header
4949 (compose, buf, sizeof(buf), str,
4950 strlen("Resent-To") + 2, TRUE);
4952 if (first_to_address) {
4953 err |= (fprintf(fp, "Resent-To: ") < 0);
4954 first_to_address = FALSE;
4956 err |= (fprintf(fp, ",") < 0);
4958 err |= (fprintf(fp, "%s", buf) < 0);
4962 if (!first_to_address) {
4963 err |= (fprintf(fp, "\n") < 0);
4966 first_cc_address = TRUE;
4967 for (list = compose->header_list; list; list = list->next) {
4968 headerentry = ((ComposeHeaderEntry *)list->data);
4969 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
4971 if (g_utf8_collate(headerentryname, cc_hdr) == 0) {
4972 const gchar *strg = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
4973 Xstrdup_a(str, strg, return -1);
4975 if (str[0] != '\0') {
4976 compose_convert_header
4977 (compose, buf, sizeof(buf), str,
4978 strlen("Resent-Cc") + 2, TRUE);
4980 if (first_cc_address) {
4981 err |= (fprintf(fp, "Resent-Cc: ") < 0);
4982 first_cc_address = FALSE;
4984 err |= (fprintf(fp, ",") < 0);
4986 err |= (fprintf(fp, "%s", buf) < 0);
4990 if (!first_cc_address) {
4991 err |= (fprintf(fp, "\n") < 0);
4994 return (err ? -1:0);
4997 static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
4999 gchar buf[BUFFSIZE];
5001 const gchar *entstr;
5002 /* struct utsname utsbuf; */
5003 gboolean err = FALSE;
5005 g_return_val_if_fail(fp != NULL, -1);
5006 g_return_val_if_fail(compose->account != NULL, -1);
5007 g_return_val_if_fail(compose->account->address != NULL, -1);
5010 get_rfc822_date(buf, sizeof(buf));
5011 err |= (fprintf(fp, "Resent-Date: %s\n", buf) < 0);
5014 if (compose->account->name && *compose->account->name) {
5015 compose_convert_header
5016 (compose, buf, sizeof(buf), compose->account->name,
5017 strlen("From: "), TRUE);
5018 err |= (fprintf(fp, "Resent-From: %s <%s>\n",
5019 buf, compose->account->address) < 0);
5021 err |= (fprintf(fp, "Resent-From: %s\n", compose->account->address) < 0);
5024 entstr = gtk_entry_get_text(GTK_ENTRY(compose->subject_entry));
5025 if (*entstr != '\0') {
5026 Xstrdup_a(str, entstr, return -1);
5029 compose_convert_header(compose, buf, sizeof(buf), str,
5030 strlen("Subject: "), FALSE);
5031 err |= (fprintf(fp, "Subject: %s\n", buf) < 0);
5035 /* Resent-Message-ID */
5036 if (compose->account->set_domain && compose->account->domain) {
5037 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5038 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5039 g_snprintf(buf, sizeof(buf), "%s",
5040 strchr(compose->account->address, '@') ?
5041 strchr(compose->account->address, '@')+1 :
5042 compose->account->address);
5044 g_snprintf(buf, sizeof(buf), "%s", "");
5047 if (compose->account->gen_msgid) {
5048 generate_msgid(buf, sizeof(buf));
5049 err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
5050 compose->msgid = g_strdup(buf);
5052 compose->msgid = NULL;
5055 if (compose_redirect_write_headers_from_headerlist(compose, fp))
5058 /* separator between header and body */
5059 err |= (fputs("\n", fp) == EOF);
5061 return (err ? -1:0);
5064 static gint compose_redirect_write_to_file(Compose *compose, FILE *fdest)
5068 gchar buf[BUFFSIZE];
5070 gboolean skip = FALSE;
5071 gboolean err = FALSE;
5072 gchar *not_included[]={
5073 "Return-Path:", "Delivered-To:", "Received:",
5074 "Subject:", "X-UIDL:", "AF:",
5075 "NF:", "PS:", "SRH:",
5076 "SFN:", "DSR:", "MID:",
5077 "CFG:", "PT:", "S:",
5078 "RQ:", "SSV:", "NSV:",
5079 "SSH:", "R:", "MAID:",
5080 "NAID:", "RMID:", "FMID:",
5081 "SCF:", "RRCPT:", "NG:",
5082 "X-Claws-Privacy", "X-Claws-Sign:", "X-Claws-Encrypt",
5083 "X-Claws-End-Special-Headers:", "X-Claws-Account-Id:",
5084 "X-Sylpheed-Privacy", "X-Sylpheed-Sign:", "X-Sylpheed-Encrypt",
5085 "X-Sylpheed-End-Special-Headers:", "X-Sylpheed-Account-Id:",
5088 if ((fp = g_fopen(compose->redirect_filename, "rb")) == NULL) {
5089 FILE_OP_ERROR(compose->redirect_filename, "fopen");
5093 while (procheader_get_one_field_asis(buf, sizeof(buf), fp) != -1) {
5095 for (i = 0; not_included[i] != NULL; i++) {
5096 if (g_ascii_strncasecmp(buf, not_included[i],
5097 strlen(not_included[i])) == 0) {
5104 if (fputs(buf, fdest) == -1)
5107 if (!prefs_common.redirect_keep_from) {
5108 if (g_ascii_strncasecmp(buf, "From:",
5109 strlen("From:")) == 0) {
5110 err |= (fputs(" (by way of ", fdest) == EOF);
5111 if (compose->account->name
5112 && *compose->account->name) {
5113 compose_convert_header
5114 (compose, buf, sizeof(buf),
5115 compose->account->name,
5118 err |= (fprintf(fdest, "%s <%s>",
5120 compose->account->address) < 0);
5122 err |= (fprintf(fdest, "%s",
5123 compose->account->address) < 0);
5124 err |= (fputs(")", fdest) == EOF);
5128 if (fputs("\n", fdest) == -1)
5135 if (compose_redirect_write_headers(compose, fdest))
5138 while ((len = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
5139 if (fwrite(buf, sizeof(gchar), len, fdest) != len)
5152 static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gboolean attach_parts)
5154 GtkTextBuffer *buffer;
5155 GtkTextIter start, end;
5158 const gchar *out_codeset;
5159 EncodingType encoding = ENC_UNKNOWN;
5160 MimeInfo *mimemsg, *mimetext;
5162 const gchar *src_codeset = CS_INTERNAL;
5164 if (action == COMPOSE_WRITE_FOR_SEND)
5165 attach_parts = TRUE;
5167 /* create message MimeInfo */
5168 mimemsg = procmime_mimeinfo_new();
5169 mimemsg->type = MIMETYPE_MESSAGE;
5170 mimemsg->subtype = g_strdup("rfc822");
5171 mimemsg->content = MIMECONTENT_MEM;
5172 mimemsg->tmp = TRUE; /* must free content later */
5173 mimemsg->data.mem = compose_get_header(compose);
5175 /* Create text part MimeInfo */
5176 /* get all composed text */
5177 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5178 gtk_text_buffer_get_start_iter(buffer, &start);
5179 gtk_text_buffer_get_end_iter(buffer, &end);
5180 chars = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5182 out_codeset = conv_get_charset_str(compose->out_encoding);
5184 if (!out_codeset && is_ascii_str(chars)) {
5185 out_codeset = CS_US_ASCII;
5186 } else if (prefs_common.outgoing_fallback_to_ascii &&
5187 is_ascii_str(chars)) {
5188 out_codeset = CS_US_ASCII;
5189 encoding = ENC_7BIT;
5193 gchar *test_conv_global_out = NULL;
5194 gchar *test_conv_reply = NULL;
5196 /* automatic mode. be automatic. */
5197 codeconv_set_strict(TRUE);
5199 out_codeset = conv_get_outgoing_charset_str();
5201 debug_print("trying to convert to %s\n", out_codeset);
5202 test_conv_global_out = conv_codeset_strdup(chars, src_codeset, out_codeset);
5205 if (!test_conv_global_out && compose->orig_charset
5206 && strcmp(compose->orig_charset, CS_US_ASCII)) {
5207 out_codeset = compose->orig_charset;
5208 debug_print("failure; trying to convert to %s\n", out_codeset);
5209 test_conv_reply = conv_codeset_strdup(chars, src_codeset, out_codeset);
5212 if (!test_conv_global_out && !test_conv_reply) {
5214 out_codeset = CS_INTERNAL;
5215 debug_print("failure; finally using %s\n", out_codeset);
5217 g_free(test_conv_global_out);
5218 g_free(test_conv_reply);
5219 codeconv_set_strict(FALSE);
5222 if (encoding == ENC_UNKNOWN) {
5223 if (prefs_common.encoding_method == CTE_BASE64)
5224 encoding = ENC_BASE64;
5225 else if (prefs_common.encoding_method == CTE_QUOTED_PRINTABLE)
5226 encoding = ENC_QUOTED_PRINTABLE;
5227 else if (prefs_common.encoding_method == CTE_8BIT)
5228 encoding = ENC_8BIT;
5230 encoding = procmime_get_encoding_for_charset(out_codeset);
5233 debug_print("src encoding = %s, out encoding = %s, transfer encoding = %s\n",
5234 src_codeset, out_codeset, procmime_get_encoding_str(encoding));
5236 if (action == COMPOSE_WRITE_FOR_SEND) {
5237 codeconv_set_strict(TRUE);
5238 buf = conv_codeset_strdup(chars, src_codeset, out_codeset);
5239 codeconv_set_strict(FALSE);
5245 msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
5246 "to the specified %s charset.\n"
5247 "Send it as %s?"), out_codeset, src_codeset);
5248 aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
5249 NULL, ALERT_ERROR, G_ALERTDEFAULT);
5252 if (aval != G_ALERTALTERNATE) {
5257 out_codeset = src_codeset;
5263 out_codeset = src_codeset;
5268 if (encoding == ENC_8BIT || encoding == ENC_7BIT) {
5269 if (!strncmp(buf, "From ", sizeof("From ")-1) ||
5270 strstr(buf, "\nFrom ") != NULL) {
5271 encoding = ENC_QUOTED_PRINTABLE;
5275 mimetext = procmime_mimeinfo_new();
5276 mimetext->content = MIMECONTENT_MEM;
5277 mimetext->tmp = TRUE; /* must free content later */
5278 /* dup'ed because procmime_encode_content can turn it into a tmpfile
5279 * and free the data, which we need later. */
5280 mimetext->data.mem = g_strdup(buf);
5281 mimetext->type = MIMETYPE_TEXT;
5282 mimetext->subtype = g_strdup("plain");
5283 g_hash_table_insert(mimetext->typeparameters, g_strdup("charset"),
5284 g_strdup(out_codeset));
5286 /* protect trailing spaces when signing message */
5287 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5288 privacy_system_can_sign(compose->privacy_system)) {
5289 encoding = ENC_QUOTED_PRINTABLE;
5292 debug_print("main text: %zd bytes encoded as %s in %d\n",
5293 strlen(buf), out_codeset, encoding);
5295 /* check for line length limit */
5296 if (action == COMPOSE_WRITE_FOR_SEND &&
5297 encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
5298 check_line_length(buf, 1000, &line) < 0) {
5302 msg = g_strdup_printf
5303 (_("Line %d exceeds the line length limit (998 bytes).\n"
5304 "The contents of the message might be broken on the way to the delivery.\n"
5306 "Send it anyway?"), line + 1);
5307 aval = alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
5309 if (aval != G_ALERTALTERNATE) {
5315 if (encoding != ENC_UNKNOWN)
5316 procmime_encode_content(mimetext, encoding);
5318 /* append attachment parts */
5319 if (compose_use_attach(compose) && attach_parts) {
5320 MimeInfo *mimempart;
5321 gchar *boundary = NULL;
5322 mimempart = procmime_mimeinfo_new();
5323 mimempart->content = MIMECONTENT_EMPTY;
5324 mimempart->type = MIMETYPE_MULTIPART;
5325 mimempart->subtype = g_strdup("mixed");
5329 boundary = generate_mime_boundary(NULL);
5330 } while (strstr(buf, boundary) != NULL);
5332 g_hash_table_insert(mimempart->typeparameters, g_strdup("boundary"),
5335 mimetext->disposition = DISPOSITIONTYPE_INLINE;
5337 g_node_append(mimempart->node, mimetext->node);
5338 g_node_append(mimemsg->node, mimempart->node);
5340 compose_add_attachments(compose, mimempart);
5342 g_node_append(mimemsg->node, mimetext->node);
5346 /* sign message if sending */
5347 if (action == COMPOSE_WRITE_FOR_SEND && compose->use_signing &&
5348 privacy_system_can_sign(compose->privacy_system))
5349 if (!privacy_sign(compose->privacy_system, mimemsg, compose->account))
5352 procmime_write_mimeinfo(mimemsg, fp);
5354 procmime_mimeinfo_free_all(mimemsg);
5359 static gint compose_write_body_to_file(Compose *compose, const gchar *file)
5361 GtkTextBuffer *buffer;
5362 GtkTextIter start, end;
5367 if ((fp = g_fopen(file, "wb")) == NULL) {
5368 FILE_OP_ERROR(file, "fopen");
5372 /* chmod for security */
5373 if (change_file_mode_rw(fp, file) < 0) {
5374 FILE_OP_ERROR(file, "chmod");
5375 g_warning("can't change file mode\n");
5378 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
5379 gtk_text_buffer_get_start_iter(buffer, &start);
5380 gtk_text_buffer_get_end_iter(buffer, &end);
5381 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
5383 chars = conv_codeset_strdup
5384 (tmp, CS_INTERNAL, conv_get_locale_charset_str());
5387 if (!chars) return -1;
5390 len = strlen(chars);
5391 if (fwrite(chars, sizeof(gchar), len, fp) != len) {
5392 FILE_OP_ERROR(file, "fwrite");
5401 if (fclose(fp) == EOF) {
5402 FILE_OP_ERROR(file, "fclose");
5409 static gint compose_remove_reedit_target(Compose *compose, gboolean force)
5412 MsgInfo *msginfo = compose->targetinfo;
5414 g_return_val_if_fail(compose->mode == COMPOSE_REEDIT, -1);
5415 if (!msginfo) return -1;
5417 if (!force && MSG_IS_LOCKED(msginfo->flags))
5420 item = msginfo->folder;
5421 g_return_val_if_fail(item != NULL, -1);
5423 if (procmsg_msg_exist(msginfo) &&
5424 (folder_has_parent_of_type(item, F_QUEUE) ||
5425 folder_has_parent_of_type(item, F_DRAFT)
5426 || msginfo == compose->autosaved_draft)) {
5427 if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
5428 g_warning("can't remove the old message\n");
5431 debug_print("removed reedit target %d\n", msginfo->msgnum);
5438 static void compose_remove_draft(Compose *compose)
5441 MsgInfo *msginfo = compose->targetinfo;
5442 drafts = account_get_special_folder(compose->account, F_DRAFT);
5444 if (procmsg_msg_exist(msginfo)) {
5445 folder_item_remove_msg(drafts, msginfo->msgnum);
5450 gint compose_queue(Compose *compose, gint *msgnum, FolderItem **item, gchar **msgpath,
5451 gboolean remove_reedit_target)
5453 return compose_queue_sub (compose, msgnum, item, msgpath, FALSE, remove_reedit_target);
5456 static gboolean compose_warn_encryption(Compose *compose)
5458 const gchar *warning = privacy_get_encrypt_warning(compose->privacy_system);
5459 AlertValue val = G_ALERTALTERNATE;
5461 if (warning == NULL)
5464 val = alertpanel_full(_("Encryption warning"), warning,
5465 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
5466 TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
5467 if (val & G_ALERTDISABLE) {
5468 val &= ~G_ALERTDISABLE;
5469 if (val == G_ALERTALTERNATE)
5470 privacy_inhibit_encrypt_warning(compose->privacy_system,
5474 if (val == G_ALERTALTERNATE) {
5481 static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
5482 gchar **msgpath, gboolean check_subject,
5483 gboolean remove_reedit_target)
5490 static gboolean lock = FALSE;
5491 PrefsAccount *mailac = NULL, *newsac = NULL;
5492 gboolean err = FALSE;
5494 debug_print("queueing message...\n");
5495 g_return_val_if_fail(compose->account != NULL, -1);
5499 if (compose_check_entries(compose, check_subject) == FALSE) {
5501 if (compose->batch) {
5502 gtk_widget_show_all(compose->window);
5507 if (!compose->to_list && !compose->newsgroup_list) {
5508 g_warning("can't get recipient list.");
5513 if (compose->to_list) {
5514 if (compose->account->protocol != A_NNTP)
5515 mailac = compose->account;
5516 else if (cur_account && cur_account->protocol != A_NNTP)
5517 mailac = cur_account;
5518 else if (!(mailac = compose_current_mail_account())) {
5520 alertpanel_error(_("No account for sending mails available!"));
5525 if (compose->newsgroup_list) {
5526 if (compose->account->protocol == A_NNTP)
5527 newsac = compose->account;
5528 else if (!newsac->protocol != A_NNTP) {
5530 alertpanel_error(_("No account for posting news available!"));
5535 /* write queue header */
5536 tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
5537 G_DIR_SEPARATOR, compose, (guint) rand());
5538 debug_print("queuing to %s\n", tmp);
5539 if ((fp = g_fopen(tmp, "wb")) == NULL) {
5540 FILE_OP_ERROR(tmp, "fopen");
5546 if (change_file_mode_rw(fp, tmp) < 0) {
5547 FILE_OP_ERROR(tmp, "chmod");
5548 g_warning("can't change file mode\n");
5551 /* queueing variables */
5552 err |= (fprintf(fp, "AF:\n") < 0);
5553 err |= (fprintf(fp, "NF:0\n") < 0);
5554 err |= (fprintf(fp, "PS:10\n") < 0);
5555 err |= (fprintf(fp, "SRH:1\n") < 0);
5556 err |= (fprintf(fp, "SFN:\n") < 0);
5557 err |= (fprintf(fp, "DSR:\n") < 0);
5559 err |= (fprintf(fp, "MID:<%s>\n", compose->msgid) < 0);
5561 err |= (fprintf(fp, "MID:\n") < 0);
5562 err |= (fprintf(fp, "CFG:\n") < 0);
5563 err |= (fprintf(fp, "PT:0\n") < 0);
5564 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
5565 err |= (fprintf(fp, "RQ:\n") < 0);
5567 err |= (fprintf(fp, "SSV:%s\n", mailac->smtp_server) < 0);
5569 err |= (fprintf(fp, "SSV:\n") < 0);
5571 err |= (fprintf(fp, "NSV:%s\n", newsac->nntp_server) < 0);
5573 err |= (fprintf(fp, "NSV:\n") < 0);
5574 err |= (fprintf(fp, "SSH:\n") < 0);
5575 /* write recepient list */
5576 if (compose->to_list) {
5577 err |= (fprintf(fp, "R:<%s>", (gchar *)compose->to_list->data) < 0);
5578 for (cur = compose->to_list->next; cur != NULL;
5580 err |= (fprintf(fp, ",<%s>", (gchar *)cur->data) < 0);
5581 err |= (fprintf(fp, "\n") < 0);
5583 /* write newsgroup list */
5584 if (compose->newsgroup_list) {
5585 err |= (fprintf(fp, "NG:") < 0);
5586 err |= (fprintf(fp, "%s", (gchar *)compose->newsgroup_list->data) < 0);
5587 for (cur = compose->newsgroup_list->next; cur != NULL; cur = cur->next)
5588 err |= (fprintf(fp, ",%s", (gchar *)cur->data) < 0);
5589 err |= (fprintf(fp, "\n") < 0);
5591 /* Sylpheed account IDs */
5593 err |= (fprintf(fp, "MAID:%d\n", mailac->account_id) < 0);
5595 err |= (fprintf(fp, "NAID:%d\n", newsac->account_id) < 0);
5598 if (compose->privacy_system != NULL) {
5599 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
5600 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
5601 if (compose->use_encryption) {
5603 if (!compose_warn_encryption(compose)) {
5610 if (mailac && mailac->encrypt_to_self) {
5611 GSList *tmp_list = g_slist_copy(compose->to_list);
5612 tmp_list = g_slist_append(tmp_list, compose->account->address);
5613 encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
5614 g_slist_free(tmp_list);
5616 encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
5618 if (encdata != NULL) {
5619 if (strcmp(encdata, "_DONT_ENCRYPT_")) {
5620 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5621 err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n",
5623 } /* else we finally dont want to encrypt */
5625 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
5626 /* and if encdata was null, it means there's been a problem in
5638 /* Save copy folder */
5639 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
5640 gchar *savefolderid;
5642 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
5643 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
5644 g_free(savefolderid);
5646 /* Save copy folder */
5647 if (compose->return_receipt) {
5648 err |= (fprintf(fp, "RRCPT:1\n") < 0);
5650 /* Message-ID of message replying to */
5651 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
5654 folderid = folder_item_get_identifier(compose->replyinfo->folder);
5655 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
5658 /* Message-ID of message forwarding to */
5659 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
5662 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
5663 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
5667 /* end of headers */
5668 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
5670 if (compose->redirect_filename != NULL) {
5671 if (compose_redirect_write_to_file(compose, fp) < 0) {
5680 if ((result = compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_SEND, TRUE)) < 0) {
5685 return result - 1; /* -2 for a generic error, -3 for signing error, -4 for encoding */
5689 g_warning("failed to write queue message\n");
5696 if (fclose(fp) == EOF) {
5697 FILE_OP_ERROR(tmp, "fclose");
5704 if (item && *item) {
5707 queue = account_get_special_folder(compose->account, F_QUEUE);
5710 g_warning("can't find queue folder\n");
5716 folder_item_scan(queue);
5717 if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
5718 g_warning("can't queue the message\n");
5725 if (msgpath == NULL) {
5731 if (compose->mode == COMPOSE_REEDIT && remove_reedit_target) {
5732 compose_remove_reedit_target(compose, FALSE);
5735 if ((msgnum != NULL) && (item != NULL)) {
5743 static void compose_add_attachments(Compose *compose, MimeInfo *parent)
5746 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
5748 struct stat statbuf;
5749 gchar *type, *subtype;
5750 GtkTreeModel *model;
5753 model = gtk_tree_view_get_model(tree_view);
5755 if (!gtk_tree_model_get_iter_first(model, &iter))
5758 gtk_tree_model_get(model, &iter,
5762 mimepart = procmime_mimeinfo_new();
5763 mimepart->content = MIMECONTENT_FILE;
5764 mimepart->data.filename = g_strdup(ainfo->file);
5765 mimepart->tmp = FALSE; /* or we destroy our attachment */
5766 mimepart->offset = 0;
5768 stat(ainfo->file, &statbuf);
5769 mimepart->length = statbuf.st_size;
5771 type = g_strdup(ainfo->content_type);
5773 if (!strchr(type, '/')) {
5775 type = g_strdup("application/octet-stream");
5778 subtype = strchr(type, '/') + 1;
5779 *(subtype - 1) = '\0';
5780 mimepart->type = procmime_get_media_type(type);
5781 mimepart->subtype = g_strdup(subtype);
5784 if (mimepart->type == MIMETYPE_MESSAGE &&
5785 !g_ascii_strcasecmp(mimepart->subtype, "rfc822")) {
5786 mimepart->disposition = DISPOSITIONTYPE_INLINE;
5789 g_hash_table_insert(mimepart->typeparameters,
5790 g_strdup("name"), g_strdup(ainfo->name));
5791 g_hash_table_insert(mimepart->dispositionparameters,
5792 g_strdup("filename"), g_strdup(ainfo->name));
5793 mimepart->disposition = DISPOSITIONTYPE_ATTACHMENT;
5797 if (compose->use_signing) {
5798 if (ainfo->encoding == ENC_7BIT)
5799 ainfo->encoding = ENC_QUOTED_PRINTABLE;
5800 else if (ainfo->encoding == ENC_8BIT)
5801 ainfo->encoding = ENC_BASE64;
5804 procmime_encode_content(mimepart, ainfo->encoding);
5806 g_node_append(parent->node, mimepart->node);
5807 } while (gtk_tree_model_iter_next(model, &iter));
5810 #define IS_IN_CUSTOM_HEADER(header) \
5811 (compose->account->add_customhdr && \
5812 custom_header_find(compose->account->customhdr_list, header) != NULL)
5814 static void compose_add_headerfield_from_headerlist(Compose *compose,
5816 const gchar *fieldname,
5817 const gchar *seperator)
5819 gchar *str, *fieldname_w_colon;
5820 gboolean add_field = FALSE;
5822 ComposeHeaderEntry *headerentry;
5823 const gchar *headerentryname;
5824 const gchar *trans_fieldname;
5827 if (IS_IN_CUSTOM_HEADER(fieldname))
5830 debug_print("Adding %s-fields\n", fieldname);
5832 fieldstr = g_string_sized_new(64);
5834 fieldname_w_colon = g_strconcat(fieldname, ":", NULL);
5835 trans_fieldname = prefs_common_translated_header_name(fieldname_w_colon);
5837 for (list = compose->header_list; list; list = list->next) {
5838 headerentry = ((ComposeHeaderEntry *)list->data);
5839 headerentryname = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child));
5841 if (!g_utf8_collate(trans_fieldname, headerentryname)) {
5842 str = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
5844 if (str[0] != '\0') {
5846 g_string_append(fieldstr, seperator);
5847 g_string_append(fieldstr, str);
5856 buf = g_new0(gchar, fieldstr->len * 4 + 256);
5857 compose_convert_header
5858 (compose, buf, fieldstr->len * 4 + 256, fieldstr->str,
5859 strlen(fieldname) + 2, TRUE);
5860 g_string_append_printf(header, "%s: %s\n", fieldname, buf);
5864 g_free(fieldname_w_colon);
5865 g_string_free(fieldstr, TRUE);
5870 static gchar *compose_get_header(Compose *compose)
5872 gchar buf[BUFFSIZE];
5873 const gchar *entry_str;
5877 gchar *std_headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
5879 gchar *from_name = NULL, *from_address = NULL;
5882 g_return_val_if_fail(compose->account != NULL, NULL);
5883 g_return_val_if_fail(compose->account->address != NULL, NULL);
5885 header = g_string_sized_new(64);
5888 get_rfc822_date(buf, sizeof(buf));
5889 g_string_append_printf(header, "Date: %s\n", buf);
5893 if (compose->account->name && *compose->account->name) {
5895 QUOTE_IF_REQUIRED(buf, compose->account->name);
5896 tmp = g_strdup_printf("%s <%s>",
5897 buf, compose->account->address);
5899 tmp = g_strdup_printf("%s",
5900 compose->account->address);
5902 if (!strcmp(gtk_entry_get_text(GTK_ENTRY(compose->from_name)), tmp)
5903 || strlen(gtk_entry_get_text(GTK_ENTRY(compose->from_name))) == 0) {
5905 from_name = compose->account->name ? g_strdup(compose->account->name):NULL;
5906 from_address = g_strdup(compose->account->address);
5908 gchar *spec = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
5909 /* extract name and address */
5910 if (strstr(spec, " <") && strstr(spec, ">")) {
5911 from_address = g_strdup(strrchr(spec, '<')+1);
5912 *(strrchr(from_address, '>')) = '\0';
5913 from_name = g_strdup(spec);
5914 *(strrchr(from_name, '<')) = '\0';
5917 from_address = g_strdup(spec);
5924 if (from_name && *from_name) {
5925 compose_convert_header
5926 (compose, buf, sizeof(buf), from_name,
5927 strlen("From: "), TRUE);
5928 QUOTE_IF_REQUIRED(name, buf);
5930 g_string_append_printf(header, "From: %s <%s>\n",
5931 name, from_address);
5933 g_string_append_printf(header, "From: %s\n", from_address);
5936 g_free(from_address);
5939 compose_add_headerfield_from_headerlist(compose, header, "To", ", ");
5942 compose_add_headerfield_from_headerlist(compose, header, "Newsgroups", ",");
5945 compose_add_headerfield_from_headerlist(compose, header, "Cc", ", ");
5948 compose_add_headerfield_from_headerlist(compose, header, "Bcc", ", ");
5951 str = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
5953 if (*str != '\0' && !IS_IN_CUSTOM_HEADER("Subject")) {
5956 compose_convert_header(compose, buf, sizeof(buf), str,
5957 strlen("Subject: "), FALSE);
5958 g_string_append_printf(header, "Subject: %s\n", buf);
5964 if (compose->account->set_domain && compose->account->domain) {
5965 g_snprintf(buf, sizeof(buf), "%s", compose->account->domain);
5966 } else if (!strncmp(get_domain_name(), "localhost", strlen("localhost"))) {
5967 g_snprintf(buf, sizeof(buf), "%s",
5968 strchr(compose->account->address, '@') ?
5969 strchr(compose->account->address, '@')+1 :
5970 compose->account->address);
5972 g_snprintf(buf, sizeof(buf), "%s", "");
5975 if (compose->account->gen_msgid) {
5976 generate_msgid(buf, sizeof(buf));
5977 g_string_append_printf(header, "Message-ID: <%s>\n", buf);
5978 compose->msgid = g_strdup(buf);
5980 compose->msgid = NULL;
5983 if (compose->remove_references == FALSE) {
5985 if (compose->inreplyto && compose->to_list)
5986 g_string_append_printf(header, "In-Reply-To: <%s>\n", compose->inreplyto);
5989 if (compose->references)
5990 g_string_append_printf(header, "References: %s\n", compose->references);
5994 compose_add_headerfield_from_headerlist(compose, header, "Followup-To", ",");
5997 compose_add_headerfield_from_headerlist(compose, header, "Reply-To", ", ");
6000 if (compose->account->organization &&
6001 strlen(compose->account->organization) &&
6002 !IS_IN_CUSTOM_HEADER("Organization")) {
6003 compose_convert_header(compose, buf, sizeof(buf),
6004 compose->account->organization,
6005 strlen("Organization: "), FALSE);
6006 g_string_append_printf(header, "Organization: %s\n", buf);
6009 /* Program version and system info */
6010 if (g_slist_length(compose->to_list) && !IS_IN_CUSTOM_HEADER("X-Mailer") &&
6011 !compose->newsgroup_list) {
6012 g_string_append_printf(header, "X-Mailer: %s (GTK+ %d.%d.%d; %s)\n",
6014 gtk_major_version, gtk_minor_version, gtk_micro_version,
6017 if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
6018 g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
6020 gtk_major_version, gtk_minor_version, gtk_micro_version,
6024 /* custom headers */
6025 if (compose->account->add_customhdr) {
6028 for (cur = compose->account->customhdr_list; cur != NULL;
6030 CustomHeader *chdr = (CustomHeader *)cur->data;
6032 if (custom_header_is_allowed(chdr->name)) {
6033 compose_convert_header
6034 (compose, buf, sizeof(buf),
6035 chdr->value ? chdr->value : "",
6036 strlen(chdr->name) + 2, FALSE);
6037 g_string_append_printf(header, "%s: %s\n", chdr->name, buf);
6042 /* Automatic Faces and X-Faces */
6043 if (get_account_xface (buf, sizeof(buf), compose->account->account_name) == 0) {
6044 g_string_append_printf(header, "X-Face: %s\n", buf);
6046 else if (get_default_xface (buf, sizeof(buf)) == 0) {
6047 g_string_append_printf(header, "X-Face: %s\n", buf);
6049 if (get_account_face (buf, sizeof(buf), compose->account->account_name) == 0) {
6050 g_string_append_printf(header, "Face: %s\n", buf);
6052 else if (get_default_face (buf, sizeof(buf)) == 0) {
6053 g_string_append_printf(header, "Face: %s\n", buf);
6057 switch (compose->priority) {
6058 case PRIORITY_HIGHEST: g_string_append_printf(header, "Importance: high\n"
6059 "X-Priority: 1 (Highest)\n");
6061 case PRIORITY_HIGH: g_string_append_printf(header, "Importance: high\n"
6062 "X-Priority: 2 (High)\n");
6064 case PRIORITY_NORMAL: break;
6065 case PRIORITY_LOW: g_string_append_printf(header, "Importance: low\n"
6066 "X-Priority: 4 (Low)\n");
6068 case PRIORITY_LOWEST: g_string_append_printf(header, "Importance: low\n"
6069 "X-Priority: 5 (Lowest)\n");
6071 default: debug_print("compose: priority unknown : %d\n",
6075 /* Request Return Receipt */
6076 if (!IS_IN_CUSTOM_HEADER("Disposition-Notification-To")) {
6077 if (compose->return_receipt) {
6078 if (compose->account->name
6079 && *compose->account->name) {
6080 compose_convert_header(compose, buf, sizeof(buf),
6081 compose->account->name,
6082 strlen("Disposition-Notification-To: "),
6084 g_string_append_printf(header, "Disposition-Notification-To: %s <%s>\n", buf, compose->account->address);
6086 g_string_append_printf(header, "Disposition-Notification-To: %s\n", compose->account->address);
6090 /* get special headers */
6091 for (list = compose->header_list; list; list = list->next) {
6092 ComposeHeaderEntry *headerentry;
6095 gchar *headername_wcolon;
6096 const gchar *headername_trans;
6099 gboolean standard_header = FALSE;
6101 headerentry = ((ComposeHeaderEntry *)list->data);
6103 tmp = g_strdup(gtk_entry_get_text(GTK_ENTRY(GTK_BIN(headerentry->combo)->child)));
6105 if (*tmp == '\0' || strchr(tmp, ' ') != NULL || strchr(tmp, '\r') != NULL || strchr(tmp, '\n') != NULL) {
6110 if (!strstr(tmp, ":")) {
6111 headername_wcolon = g_strconcat(tmp, ":", NULL);
6112 headername = g_strdup(tmp);
6114 headername_wcolon = g_strdup(tmp);
6115 headername = g_strdup(strtok(tmp, ":"));
6119 entry_str = gtk_entry_get_text(GTK_ENTRY(headerentry->entry));
6120 Xstrdup_a(headervalue, entry_str, return NULL);
6121 subst_char(headervalue, '\r', ' ');
6122 subst_char(headervalue, '\n', ' ');
6123 string = std_headers;
6124 while (*string != NULL) {
6125 headername_trans = prefs_common_translated_header_name(*string);
6126 if (!strcmp(headername_trans, headername_wcolon))
6127 standard_header = TRUE;
6130 if (!standard_header && !IS_IN_CUSTOM_HEADER(headername))
6131 g_string_append_printf(header, "%s %s\n", headername_wcolon, headervalue);
6134 g_free(headername_wcolon);
6138 g_string_free(header, FALSE);
6143 #undef IS_IN_CUSTOM_HEADER
6145 static void compose_convert_header(Compose *compose, gchar *dest, gint len, gchar *src,
6146 gint header_len, gboolean addr_field)
6148 gchar *tmpstr = NULL;
6149 const gchar *out_codeset = NULL;
6151 g_return_if_fail(src != NULL);
6152 g_return_if_fail(dest != NULL);
6154 if (len < 1) return;
6156 tmpstr = g_strdup(src);
6158 subst_char(tmpstr, '\n', ' ');
6159 subst_char(tmpstr, '\r', ' ');
6162 if (!g_utf8_validate(tmpstr, -1, NULL)) {
6163 gchar *mybuf = g_malloc(strlen(tmpstr)*2 +1);
6164 conv_localetodisp(mybuf, strlen(tmpstr)*2 +1, tmpstr);
6169 codeconv_set_strict(TRUE);
6170 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6171 conv_get_charset_str(compose->out_encoding));
6172 codeconv_set_strict(FALSE);
6174 if (!dest || *dest == '\0') {
6175 gchar *test_conv_global_out = NULL;
6176 gchar *test_conv_reply = NULL;
6178 /* automatic mode. be automatic. */
6179 codeconv_set_strict(TRUE);
6181 out_codeset = conv_get_outgoing_charset_str();
6183 debug_print("trying to convert to %s\n", out_codeset);
6184 test_conv_global_out = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6187 if (!test_conv_global_out && compose->orig_charset
6188 && strcmp(compose->orig_charset, CS_US_ASCII)) {
6189 out_codeset = compose->orig_charset;
6190 debug_print("failure; trying to convert to %s\n", out_codeset);
6191 test_conv_reply = conv_codeset_strdup(src, CS_INTERNAL, out_codeset);
6194 if (!test_conv_global_out && !test_conv_reply) {
6196 out_codeset = CS_INTERNAL;
6197 debug_print("finally using %s\n", out_codeset);
6199 g_free(test_conv_global_out);
6200 g_free(test_conv_reply);
6201 conv_encode_header_full(dest, len, tmpstr, header_len, addr_field,
6203 codeconv_set_strict(FALSE);
6208 static void compose_add_to_addressbook_cb(GtkMenuItem *menuitem, gpointer user_data)
6212 g_return_if_fail(user_data != NULL);
6214 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(user_data)));
6215 g_strstrip(address);
6216 if (*address != '\0') {
6217 gchar *name = procheader_get_fromname(address);
6218 extract_address(address);
6219 addressbook_add_contact(name, address, NULL, NULL);
6224 static void compose_entry_popup_extend(GtkEntry *entry, GtkMenu *menu, gpointer user_data)
6226 GtkWidget *menuitem;
6229 g_return_if_fail(menu != NULL);
6230 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
6232 menuitem = gtk_separator_menu_item_new();
6233 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6234 gtk_widget_show(menuitem);
6236 menuitem = gtk_menu_item_new_with_mnemonic(_("Add to address _book"));
6237 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menuitem);
6239 address = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
6240 g_strstrip(address);
6241 if (*address == '\0') {
6242 gtk_widget_set_sensitive(GTK_WIDGET(menuitem), FALSE);
6245 g_signal_connect(G_OBJECT(menuitem), "activate",
6246 G_CALLBACK(compose_add_to_addressbook_cb), entry);
6247 gtk_widget_show(menuitem);
6250 static void compose_create_header_entry(Compose *compose)
6252 gchar *headers[] = {"To:", "Cc:", "Bcc:", "Newsgroups:", "Reply-To:", "Followup-To:", NULL};
6257 const gchar *header = NULL;
6258 ComposeHeaderEntry *headerentry;
6259 gboolean standard_header = FALSE;
6260 #if !(GTK_CHECK_VERSION(2,12,0))
6261 GtkTooltips *tips = compose->tooltips;
6264 headerentry = g_new0(ComposeHeaderEntry, 1);
6267 combo = gtk_combo_box_entry_new_text();
6269 while(*string != NULL) {
6270 gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
6271 (gchar*)prefs_common_translated_header_name(*string));
6274 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
6275 g_signal_connect(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6276 G_CALLBACK(compose_grab_focus_cb), compose);
6277 gtk_widget_show(combo);
6278 gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
6279 compose->header_nextrow, compose->header_nextrow+1,
6280 GTK_SHRINK, GTK_FILL, 0, 0);
6281 if (compose->header_last) {
6282 const gchar *last_header_entry = gtk_entry_get_text(
6283 GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6285 while (*string != NULL) {
6286 if (!strcmp(*string, last_header_entry))
6287 standard_header = TRUE;
6290 if (standard_header)
6291 header = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child));
6293 if (!compose->header_last || !standard_header) {
6294 switch(compose->account->protocol) {
6296 header = prefs_common_translated_header_name("Newsgroups:");
6299 header = prefs_common_translated_header_name("To:");
6304 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), header);
6306 g_signal_connect_after(G_OBJECT(GTK_BIN(combo)->child), "grab_focus",
6307 G_CALLBACK(compose_grab_focus_cb), compose);
6310 entry = gtk_entry_new();
6311 gtk_widget_show(entry);
6312 CLAWS_SET_TIP(entry,
6313 _("Use <tab> to autocomplete from addressbook"));
6314 gtk_table_attach(GTK_TABLE(compose->header_table), entry, 1, 2,
6315 compose->header_nextrow, compose->header_nextrow+1,
6316 GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
6318 g_signal_connect(G_OBJECT(entry), "key-press-event",
6319 G_CALLBACK(compose_headerentry_key_press_event_cb),
6321 g_signal_connect(G_OBJECT(entry), "changed",
6322 G_CALLBACK(compose_headerentry_changed_cb),
6324 g_signal_connect_after(G_OBJECT(entry), "grab_focus",
6325 G_CALLBACK(compose_grab_focus_cb), compose);
6328 gtk_drag_dest_set(entry, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6329 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6330 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6331 g_signal_connect(G_OBJECT(entry), "drag_data_received",
6332 G_CALLBACK(compose_header_drag_received_cb),
6334 g_signal_connect(G_OBJECT(entry), "drag-drop",
6335 G_CALLBACK(compose_drag_drop),
6337 g_signal_connect(G_OBJECT(entry), "populate-popup",
6338 G_CALLBACK(compose_entry_popup_extend),
6341 address_completion_register_entry(GTK_ENTRY(entry), TRUE);
6343 headerentry->compose = compose;
6344 headerentry->combo = combo;
6345 headerentry->entry = entry;
6346 headerentry->headernum = compose->header_nextrow;
6348 compose->header_nextrow++;
6349 compose->header_last = headerentry;
6350 compose->header_list =
6351 g_slist_append(compose->header_list,
6355 static void compose_add_header_entry(Compose *compose, const gchar *header, gchar *text)
6357 ComposeHeaderEntry *last_header;
6359 last_header = compose->header_last;
6361 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(last_header->combo)->child), header);
6362 gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
6365 static void compose_remove_header_entries(Compose *compose)
6368 for (list = compose->header_list; list; list = list->next) {
6369 ComposeHeaderEntry *headerentry =
6370 (ComposeHeaderEntry *)list->data;
6371 gtk_widget_destroy(headerentry->combo);
6372 gtk_widget_destroy(headerentry->entry);
6373 g_free(headerentry);
6375 compose->header_last = NULL;
6376 g_slist_free(compose->header_list);
6377 compose->header_list = NULL;
6378 compose->header_nextrow = 1;
6379 compose_create_header_entry(compose);
6382 static GtkWidget *compose_create_header(Compose *compose)
6384 GtkWidget *from_optmenu_hbox;
6385 GtkWidget *header_scrolledwin;
6386 GtkWidget *header_table;
6390 /* header labels and entries */
6391 header_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6392 gtk_widget_show(header_scrolledwin);
6393 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(header_scrolledwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
6395 header_table = gtk_table_new(2, 2, FALSE);
6396 gtk_widget_show(header_table);
6397 gtk_container_set_border_width(GTK_CONTAINER(header_table), BORDER_WIDTH);
6398 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(header_scrolledwin), header_table);
6399 gtk_viewport_set_shadow_type(GTK_VIEWPORT(GTK_BIN(header_scrolledwin)->child), GTK_SHADOW_NONE);
6402 /* option menu for selecting accounts */
6403 from_optmenu_hbox = compose_account_option_menu_create(compose);
6404 gtk_table_attach(GTK_TABLE(header_table), from_optmenu_hbox,
6405 0, 2, count, count + 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
6408 compose->header_table = header_table;
6409 compose->header_list = NULL;
6410 compose->header_nextrow = count;
6412 compose_create_header_entry(compose);
6414 compose->table = NULL;
6416 return header_scrolledwin ;
6419 static gboolean popup_attach_button_pressed(GtkWidget *widget, gpointer data)
6421 Compose *compose = (Compose *)data;
6422 GdkEventButton event;
6425 event.time = gtk_get_current_event_time();
6427 return attach_button_pressed(compose->attach_clist, &event, compose);
6430 static GtkWidget *compose_create_attach(Compose *compose)
6432 GtkWidget *attach_scrwin;
6433 GtkWidget *attach_clist;
6435 GtkListStore *store;
6436 GtkCellRenderer *renderer;
6437 GtkTreeViewColumn *column;
6438 GtkTreeSelection *selection;
6440 /* attachment list */
6441 attach_scrwin = gtk_scrolled_window_new(NULL, NULL);
6442 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(attach_scrwin),
6443 GTK_POLICY_AUTOMATIC,
6444 GTK_POLICY_AUTOMATIC);
6445 gtk_widget_set_size_request(attach_scrwin, -1, 80);
6447 store = gtk_list_store_new(N_ATTACH_COLS,
6452 G_TYPE_AUTO_POINTER,
6454 attach_clist = GTK_WIDGET(gtk_tree_view_new_with_model
6455 (GTK_TREE_MODEL(store)));
6456 gtk_container_add(GTK_CONTAINER(attach_scrwin), attach_clist);
6457 g_object_unref(store);
6459 renderer = gtk_cell_renderer_text_new();
6460 column = gtk_tree_view_column_new_with_attributes
6461 (_("Mime type"), renderer, "text",
6462 COL_MIMETYPE, NULL);
6463 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6465 renderer = gtk_cell_renderer_text_new();
6466 column = gtk_tree_view_column_new_with_attributes
6467 (_("Size"), renderer, "text",
6469 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6471 renderer = gtk_cell_renderer_text_new();
6472 column = gtk_tree_view_column_new_with_attributes
6473 (_("Name"), renderer, "text",
6475 gtk_tree_view_append_column(GTK_TREE_VIEW(attach_clist), column);
6477 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(attach_clist),
6478 prefs_common.use_stripes_everywhere);
6479 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(attach_clist));
6480 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
6482 g_signal_connect(G_OBJECT(attach_clist), "row_activated",
6483 G_CALLBACK(attach_selected), compose);
6484 g_signal_connect(G_OBJECT(attach_clist), "button_press_event",
6485 G_CALLBACK(attach_button_pressed), compose);
6487 g_signal_connect(G_OBJECT(attach_clist), "popup-menu",
6488 G_CALLBACK(popup_attach_button_pressed), compose);
6490 gtk_widget_tap_and_hold_setup(GTK_WIDGET(attach_clist), NULL, NULL,
6491 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6492 g_signal_connect(G_OBJECT(attach_clist), "tap-and-hold",
6493 G_CALLBACK(popup_attach_button_pressed), compose);
6495 g_signal_connect(G_OBJECT(attach_clist), "key_press_event",
6496 G_CALLBACK(attach_key_pressed), compose);
6499 gtk_drag_dest_set(attach_clist,
6500 GTK_DEST_DEFAULT_ALL, compose_mime_types,
6501 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6502 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6503 g_signal_connect(G_OBJECT(attach_clist), "drag_data_received",
6504 G_CALLBACK(compose_attach_drag_received_cb),
6506 g_signal_connect(G_OBJECT(attach_clist), "drag-drop",
6507 G_CALLBACK(compose_drag_drop),
6510 compose->attach_scrwin = attach_scrwin;
6511 compose->attach_clist = attach_clist;
6513 return attach_scrwin;
6516 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose);
6517 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose);
6519 static GtkWidget *compose_create_others(Compose *compose)
6522 GtkWidget *savemsg_checkbtn;
6523 GtkWidget *savemsg_entry;
6524 GtkWidget *savemsg_select;
6527 gchar *folderidentifier;
6529 /* Table for settings */
6530 table = gtk_table_new(3, 1, FALSE);
6531 gtk_container_set_border_width(GTK_CONTAINER(table), BORDER_WIDTH);
6532 gtk_widget_show(table);
6533 gtk_table_set_row_spacings(GTK_TABLE(table), VSPACING_NARROW);
6536 /* Save Message to folder */
6537 savemsg_checkbtn = gtk_check_button_new_with_label(_("Save Message to "));
6538 gtk_widget_show(savemsg_checkbtn);
6539 gtk_table_attach(GTK_TABLE(table), savemsg_checkbtn, 0, 1, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6540 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6541 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(savemsg_checkbtn), prefs_common.savemsg);
6543 g_signal_connect(G_OBJECT(savemsg_checkbtn), "toggled",
6544 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
6546 savemsg_entry = gtk_entry_new();
6547 gtk_widget_show(savemsg_entry);
6548 gtk_table_attach_defaults(GTK_TABLE(table), savemsg_entry, 1, 2, rowcount, rowcount + 1);
6549 gtk_editable_set_editable(GTK_EDITABLE(savemsg_entry), prefs_common.savemsg);
6550 g_signal_connect_after(G_OBJECT(savemsg_entry), "grab_focus",
6551 G_CALLBACK(compose_grab_focus_cb), compose);
6552 if (account_get_special_folder(compose->account, F_OUTBOX)) {
6553 folderidentifier = folder_item_get_identifier(account_get_special_folder
6554 (compose->account, F_OUTBOX));
6555 gtk_entry_set_text(GTK_ENTRY(savemsg_entry), folderidentifier);
6556 g_free(folderidentifier);
6559 savemsg_select = gtkut_get_browse_file_btn(_("_Browse"));
6560 gtk_widget_show(savemsg_select);
6561 gtk_table_attach(GTK_TABLE(table), savemsg_select, 2, 3, rowcount, rowcount + 1, GTK_SHRINK | GTK_FILL, GTK_SHRINK, 0, 0);
6562 g_signal_connect(G_OBJECT(savemsg_select), "clicked",
6563 G_CALLBACK(compose_savemsg_select_cb),
6568 compose->savemsg_checkbtn = savemsg_checkbtn;
6569 compose->savemsg_entry = savemsg_entry;
6574 static void compose_savemsg_checkbtn_cb(GtkWidget *widget, Compose *compose)
6576 gtk_editable_set_editable(GTK_EDITABLE(compose->savemsg_entry),
6577 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn)));
6580 static void compose_savemsg_select_cb(GtkWidget *widget, Compose *compose)
6585 dest = foldersel_folder_sel(NULL, FOLDER_SEL_COPY, NULL, FALSE);
6588 path = folder_item_get_identifier(dest);
6590 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), path);
6594 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry, gboolean wrap,
6595 GdkAtom clip, GtkTextIter *insert_place);
6598 static gboolean text_clicked(GtkWidget *text, GdkEventButton *event,
6602 GtkTextBuffer *buffer = GTK_TEXT_VIEW(text)->buffer;
6604 if (event->button == 3) {
6606 GtkTextIter sel_start, sel_end;
6607 gboolean stuff_selected;
6609 /* move the cursor to allow GtkAspell to check the word
6610 * under the mouse */
6611 if (event->x && event->y) {
6612 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6613 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6615 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6618 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
6619 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
6622 stuff_selected = gtk_text_buffer_get_selection_bounds(
6624 &sel_start, &sel_end);
6626 gtk_text_buffer_place_cursor (buffer, &iter);
6627 /* reselect stuff */
6629 && gtk_text_iter_in_range(&iter, &sel_start, &sel_end)) {
6630 gtk_text_buffer_select_range(buffer,
6631 &sel_start, &sel_end);
6633 return FALSE; /* pass the event so that the right-click goes through */
6636 if (event->button == 2) {
6641 /* get the middle-click position to paste at the correct place */
6642 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text),
6643 GTK_TEXT_WINDOW_TEXT, event->x, event->y,
6645 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW(text),
6648 entry_paste_clipboard(compose, text,
6649 prefs_common.linewrap_pastes,
6650 GDK_SELECTION_PRIMARY, &iter);
6658 static void compose_spell_menu_changed(void *data)
6660 Compose *compose = (Compose *)data;
6662 GtkWidget *menuitem;
6663 GtkWidget *parent_item;
6664 GtkMenu *menu = GTK_MENU(gtk_menu_new());
6665 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
6668 if (compose->gtkaspell == NULL)
6671 parent_item = gtk_item_factory_get_item(ifactory,
6672 "/Spelling/Options");
6674 /* setting the submenu removes /Spelling/Options from the factory
6675 * so we need to save it */
6677 if (parent_item == NULL) {
6678 parent_item = compose->aspell_options_menu;
6679 gtk_menu_item_remove_submenu(GTK_MENU_ITEM(parent_item));
6681 compose->aspell_options_menu = parent_item;
6683 spell_menu = gtkaspell_make_config_menu(compose->gtkaspell);
6685 spell_menu = g_slist_reverse(spell_menu);
6686 for (items = spell_menu;
6687 items; items = items->next) {
6688 menuitem = GTK_WIDGET(GTK_MENU_ITEM(items->data));
6689 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
6690 gtk_widget_show(GTK_WIDGET(menuitem));
6692 g_slist_free(spell_menu);
6694 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent_item), GTK_WIDGET(menu));
6699 static gboolean compose_popup_menu(GtkWidget *widget, gpointer data)
6701 Compose *compose = (Compose *)data;
6702 GdkEventButton event;
6705 event.time = gtk_get_current_event_time();
6709 return text_clicked(compose->text, &event, compose);
6712 static gboolean compose_force_window_origin = TRUE;
6713 static Compose *compose_create(PrefsAccount *account,
6722 GtkWidget *handlebox;
6724 GtkWidget *notebook;
6726 GtkWidget *attach_hbox;
6727 GtkWidget *attach_lab1;
6728 GtkWidget *attach_lab2;
6733 GtkWidget *subject_hbox;
6734 GtkWidget *subject_frame;
6735 GtkWidget *subject_entry;
6739 GtkWidget *edit_vbox;
6740 GtkWidget *ruler_hbox;
6742 GtkWidget *scrolledwin;
6744 GtkTextBuffer *buffer;
6745 GtkClipboard *clipboard;
6748 UndoMain *undostruct;
6750 gchar *titles[N_ATTACH_COLS];
6751 guint n_menu_entries;
6752 GtkWidget *popupmenu;
6753 GtkItemFactory *popupfactory;
6754 GtkItemFactory *ifactory;
6755 GtkWidget *tmpl_menu;
6757 GtkWidget *menuitem;
6760 GtkAspell * gtkaspell = NULL;
6763 static GdkGeometry geometry;
6765 g_return_val_if_fail(account != NULL, NULL);
6767 debug_print("Creating compose window...\n");
6768 compose = g_new0(Compose, 1);
6770 titles[COL_MIMETYPE] = _("MIME type");
6771 titles[COL_SIZE] = _("Size");
6772 titles[COL_NAME] = _("Name");
6774 compose->batch = batch;
6775 compose->account = account;
6776 compose->folder = folder;
6778 compose->mutex = g_mutex_new();
6779 compose->set_cursor_pos = -1;
6781 #if !(GTK_CHECK_VERSION(2,12,0))
6782 compose->tooltips = tips;
6785 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose");
6787 gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
6788 gtk_widget_set_size_request(window, -1, prefs_common.compose_height);
6790 if (!geometry.max_width) {
6791 geometry.max_width = gdk_screen_width();
6792 geometry.max_height = gdk_screen_height();
6795 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6796 &geometry, GDK_HINT_MAX_SIZE);
6797 if (!geometry.min_width) {
6798 geometry.min_width = 600;
6799 geometry.min_height = 480;
6801 gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL,
6802 &geometry, GDK_HINT_MIN_SIZE);
6804 #ifndef GENERIC_UMPC
6805 if (compose_force_window_origin)
6806 gtk_widget_set_uposition(window, prefs_common.compose_x,
6807 prefs_common.compose_y);
6809 g_signal_connect(G_OBJECT(window), "delete_event",
6810 G_CALLBACK(compose_delete_cb), compose);
6811 MANAGE_WINDOW_SIGNALS_CONNECT(window);
6812 gtk_widget_realize(window);
6814 gtkut_widget_set_composer_icon(window);
6816 vbox = gtk_vbox_new(FALSE, 0);
6817 gtk_container_add(GTK_CONTAINER(window), vbox);
6819 n_menu_entries = sizeof(compose_entries) / sizeof(compose_entries[0]);
6820 menubar = menubar_create(window, compose_entries,
6821 n_menu_entries, "<Compose>", compose);
6822 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
6824 if (prefs_common.toolbar_detachable) {
6825 handlebox = gtk_handle_box_new();
6827 handlebox = gtk_hbox_new(FALSE, 0);
6829 gtk_box_pack_start(GTK_BOX(vbox), handlebox, FALSE, FALSE, 0);
6831 gtk_widget_realize(handlebox);
6833 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, window,
6836 compose->toolbar = toolbar_create(TOOLBAR_COMPOSE, handlebox,
6840 vbox2 = gtk_vbox_new(FALSE, 2);
6841 gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0);
6842 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
6845 notebook = gtk_notebook_new();
6846 gtk_widget_set_size_request(notebook, -1, 130);
6847 gtk_widget_show(notebook);
6849 /* header labels and entries */
6850 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6851 compose_create_header(compose),
6852 gtk_label_new_with_mnemonic(_("Hea_der")));
6853 /* attachment list */
6854 attach_hbox = gtk_hbox_new(FALSE, 0);
6855 gtk_widget_show(attach_hbox);
6857 attach_lab1 = gtk_label_new_with_mnemonic(_("_Attachments"));
6858 gtk_widget_show(attach_lab1);
6859 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab1, TRUE, TRUE, 0);
6861 attach_lab2 = gtk_label_new("");
6862 gtk_widget_show(attach_lab2);
6863 gtk_box_pack_start(GTK_BOX(attach_hbox), attach_lab2, FALSE, FALSE, 0);
6865 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6866 compose_create_attach(compose),
6869 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
6870 compose_create_others(compose),
6871 gtk_label_new_with_mnemonic(_("Othe_rs")));
6874 subject_hbox = gtk_hbox_new(FALSE, 0);
6875 gtk_widget_show(subject_hbox);
6877 subject_frame = gtk_frame_new(NULL);
6878 gtk_frame_set_shadow_type(GTK_FRAME(subject_frame), GTK_SHADOW_NONE);
6879 gtk_box_pack_start(GTK_BOX(subject_hbox), subject_frame, TRUE, TRUE, 0);
6880 gtk_widget_show(subject_frame);
6882 subject = gtk_hbox_new(FALSE, HSPACING_NARROW);
6883 gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
6884 gtk_widget_show(subject);
6886 label = gtk_label_new(_("Subject:"));
6887 gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
6888 gtk_widget_show(label);
6890 subject_entry = gtk_entry_new();
6891 gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
6892 g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
6893 G_CALLBACK(compose_grab_focus_cb), compose);
6894 gtk_widget_show(subject_entry);
6895 compose->subject_entry = subject_entry;
6896 gtk_container_add(GTK_CONTAINER(subject_frame), subject);
6898 edit_vbox = gtk_vbox_new(FALSE, 0);
6900 gtk_box_pack_start(GTK_BOX(edit_vbox), subject_hbox, FALSE, FALSE, 0);
6903 ruler_hbox = gtk_hbox_new(FALSE, 0);
6904 gtk_box_pack_start(GTK_BOX(edit_vbox), ruler_hbox, FALSE, FALSE, 0);
6906 ruler = gtk_shruler_new();
6907 gtk_ruler_set_range(GTK_RULER(ruler), 0.0, 100.0, 1.0, 100.0);
6908 gtk_box_pack_start(GTK_BOX(ruler_hbox), ruler, TRUE, TRUE,
6912 scrolledwin = gtk_scrolled_window_new(NULL, NULL);
6913 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
6914 GTK_POLICY_AUTOMATIC,
6915 GTK_POLICY_AUTOMATIC);
6916 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin),
6918 gtk_box_pack_start(GTK_BOX(edit_vbox), scrolledwin, TRUE, TRUE, 0);
6919 gtk_widget_set_size_request(scrolledwin, prefs_common.compose_width, -1);
6921 text = gtk_text_view_new();
6922 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
6923 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text), GTK_WRAP_WORD_CHAR);
6924 gtk_text_view_set_editable(GTK_TEXT_VIEW(text), TRUE);
6925 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
6926 gtk_text_buffer_add_selection_clipboard(buffer, clipboard);
6928 gtk_container_add(GTK_CONTAINER(scrolledwin), text);
6930 g_signal_connect_after(G_OBJECT(text), "size_allocate",
6931 G_CALLBACK(compose_edit_size_alloc),
6933 g_signal_connect(G_OBJECT(buffer), "changed",
6934 G_CALLBACK(compose_changed_cb), compose);
6935 g_signal_connect(G_OBJECT(text), "grab_focus",
6936 G_CALLBACK(compose_grab_focus_cb), compose);
6937 g_signal_connect(G_OBJECT(buffer), "insert_text",
6938 G_CALLBACK(text_inserted), compose);
6939 g_signal_connect(G_OBJECT(text), "button_press_event",
6940 G_CALLBACK(text_clicked), compose);
6942 g_signal_connect(G_OBJECT(text), "popup-menu",
6943 G_CALLBACK(compose_popup_menu), compose);
6945 gtk_widget_tap_and_hold_setup(GTK_WIDGET(text), NULL, NULL,
6946 GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
6947 g_signal_connect(G_OBJECT(text), "tap-and-hold",
6948 G_CALLBACK(compose_popup_menu), compose);
6950 g_signal_connect(G_OBJECT(subject_entry), "changed",
6951 G_CALLBACK(compose_changed_cb), compose);
6954 gtk_drag_dest_set(text, GTK_DEST_DEFAULT_ALL, compose_mime_types,
6955 sizeof(compose_mime_types)/sizeof(compose_mime_types[0]),
6956 GDK_ACTION_COPY | GDK_ACTION_MOVE);
6957 g_signal_connect(G_OBJECT(text), "drag_data_received",
6958 G_CALLBACK(compose_insert_drag_received_cb),
6960 g_signal_connect(G_OBJECT(text), "drag-drop",
6961 G_CALLBACK(compose_drag_drop),
6963 gtk_widget_show_all(vbox);
6965 /* pane between attach clist and text */
6966 paned = gtk_vpaned_new();
6967 gtk_paned_set_gutter_size(GTK_PANED(paned), 12);
6968 gtk_container_add(GTK_CONTAINER(vbox2), paned);
6970 if( maemo_mainwindow_is_fullscreen(mainwindow_get_mainwindow()->window) )
6971 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 300 : 280);
6973 gtk_widget_set_size_request(edit_vbox, -1, mode == COMPOSE_NEW ? 250 : 230);
6975 gtk_paned_add1(GTK_PANED(paned), notebook);
6976 gtk_paned_add2(GTK_PANED(paned), edit_vbox);
6977 gtk_widget_show_all(paned);
6980 if (prefs_common.textfont) {
6981 PangoFontDescription *font_desc;
6983 font_desc = pango_font_description_from_string
6984 (prefs_common.textfont);
6986 gtk_widget_modify_font(text, font_desc);
6987 pango_font_description_free(font_desc);
6991 n_entries = sizeof(compose_popup_entries) /
6992 sizeof(compose_popup_entries[0]);
6993 popupmenu = menu_create_items(compose_popup_entries, n_entries,
6994 "<Compose>", &popupfactory,
6997 ifactory = gtk_item_factory_from_widget(menubar);
6998 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
6999 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
7000 menu_set_sensitive(ifactory, "/Options/Remove references", FALSE);
7002 tmpl_menu = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7004 undostruct = undo_init(text);
7005 undo_set_change_state_func(undostruct, &compose_undo_state_changed,
7008 address_completion_start(window);
7010 compose->window = window;
7011 compose->vbox = vbox;
7012 compose->menubar = menubar;
7013 compose->handlebox = handlebox;
7015 compose->vbox2 = vbox2;
7017 compose->paned = paned;
7019 compose->attach_label = attach_lab2;
7021 compose->notebook = notebook;
7022 compose->edit_vbox = edit_vbox;
7023 compose->ruler_hbox = ruler_hbox;
7024 compose->ruler = ruler;
7025 compose->scrolledwin = scrolledwin;
7026 compose->text = text;
7028 compose->focused_editable = NULL;
7030 compose->popupmenu = popupmenu;
7031 compose->popupfactory = popupfactory;
7033 compose->tmpl_menu = tmpl_menu;
7035 compose->mode = mode;
7036 compose->rmode = mode;
7038 compose->targetinfo = NULL;
7039 compose->replyinfo = NULL;
7040 compose->fwdinfo = NULL;
7042 compose->replyto = NULL;
7044 compose->bcc = NULL;
7045 compose->followup_to = NULL;
7047 compose->ml_post = NULL;
7049 compose->inreplyto = NULL;
7050 compose->references = NULL;
7051 compose->msgid = NULL;
7052 compose->boundary = NULL;
7054 compose->autowrap = prefs_common.autowrap;
7056 compose->use_signing = FALSE;
7057 compose->use_encryption = FALSE;
7058 compose->privacy_system = NULL;
7060 compose->modified = FALSE;
7062 compose->return_receipt = FALSE;
7064 compose->to_list = NULL;
7065 compose->newsgroup_list = NULL;
7067 compose->undostruct = undostruct;
7069 compose->sig_str = NULL;
7071 compose->exteditor_file = NULL;
7072 compose->exteditor_pid = -1;
7073 compose->exteditor_tag = -1;
7074 compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
7077 menu_set_sensitive(ifactory, "/Spelling", FALSE);
7078 if (mode != COMPOSE_REDIRECT) {
7079 if (prefs_common.enable_aspell && prefs_common.dictionary &&
7080 strcmp(prefs_common.dictionary, "")) {
7081 gtkaspell = gtkaspell_new(prefs_common.aspell_path,
7082 prefs_common.dictionary,
7083 prefs_common.alt_dictionary,
7084 conv_get_locale_charset_str(),
7085 prefs_common.misspelled_col,
7086 prefs_common.check_while_typing,
7087 prefs_common.recheck_when_changing_dict,
7088 prefs_common.use_alternate,
7089 prefs_common.use_both_dicts,
7090 GTK_TEXT_VIEW(text),
7091 GTK_WINDOW(compose->window),
7092 compose_spell_menu_changed,
7095 alertpanel_error(_("Spell checker could not "
7097 gtkaspell_checkers_strerror());
7098 gtkaspell_checkers_reset_error();
7100 if (!gtkaspell_set_sug_mode(gtkaspell,
7101 prefs_common.aspell_sugmode)) {
7102 debug_print("Aspell: could not set "
7103 "suggestion mode %s\n",
7104 gtkaspell_checkers_strerror());
7105 gtkaspell_checkers_reset_error();
7108 menu_set_sensitive(ifactory, "/Spelling", TRUE);
7112 compose->gtkaspell = gtkaspell;
7113 compose_spell_menu_changed(compose);
7116 compose_select_account(compose, account, TRUE);
7118 menu_set_active(ifactory, "/Edit/Auto wrapping", prefs_common.autowrap);
7119 if (account->set_autocc && account->auto_cc && mode != COMPOSE_REEDIT)
7120 compose_entry_append(compose, account->auto_cc, COMPOSE_CC);
7122 if (account->set_autobcc && account->auto_bcc && mode != COMPOSE_REEDIT)
7123 compose_entry_append(compose, account->auto_bcc, COMPOSE_BCC);
7125 if (account->set_autoreplyto && account->auto_replyto && mode != COMPOSE_REEDIT)
7126 compose_entry_append(compose, account->auto_replyto, COMPOSE_REPLYTO);
7128 menu_set_sensitive(ifactory, "/Options/Reply mode", compose->mode == COMPOSE_REPLY);
7130 if (account->protocol != A_NNTP)
7131 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7132 prefs_common_translated_header_name("To:"));
7134 gtk_entry_set_text(GTK_ENTRY(GTK_BIN(compose->header_last->combo)->child),
7135 prefs_common_translated_header_name("Newsgroups:"));
7137 addressbook_set_target_compose(compose);
7139 if (mode != COMPOSE_REDIRECT)
7140 compose_set_template_menu(compose);
7142 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Template");
7143 menu_set_sensitive(ifactory, "/Tools/Template", FALSE);
7146 compose_list = g_list_append(compose_list, compose);
7148 if (!prefs_common.show_ruler)
7149 gtk_widget_hide(ruler_hbox);
7151 menuitem = gtk_item_factory_get_item(ifactory, "/Tools/Show ruler");
7152 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
7153 prefs_common.show_ruler);
7156 compose->priority = PRIORITY_NORMAL;
7157 compose_update_priority_menu_item(compose);
7159 compose_set_out_encoding(compose);
7162 compose_update_actions_menu(compose);
7164 /* Privacy Systems menu */
7165 compose_update_privacy_systems_menu(compose);
7167 activate_privacy_system(compose, account, TRUE);
7168 toolbar_set_style(compose->toolbar->toolbar, compose->handlebox, prefs_common.toolbar_style);
7170 gtk_widget_realize(window);
7172 gtk_widget_show(window);
7174 maemo_window_full_screen_if_needed(GTK_WINDOW(window));
7175 maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
7182 static GtkWidget *compose_account_option_menu_create(Compose *compose)
7187 GtkWidget *optmenubox;
7190 GtkWidget *from_name = NULL;
7191 #if !(GTK_CHECK_VERSION(2,12,0))
7192 GtkTooltips *tips = compose->tooltips;
7195 gint num = 0, def_menu = 0;
7197 accounts = account_get_list();
7198 g_return_val_if_fail(accounts != NULL, NULL);
7200 optmenubox = gtk_event_box_new();
7201 optmenu = gtkut_sc_combobox_create(optmenubox, FALSE);
7202 menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
7204 hbox = gtk_hbox_new(FALSE, 6);
7205 from_name = gtk_entry_new();
7207 g_signal_connect_after(G_OBJECT(from_name), "grab_focus",
7208 G_CALLBACK(compose_grab_focus_cb), compose);
7210 for (; accounts != NULL; accounts = accounts->next, num++) {
7211 PrefsAccount *ac = (PrefsAccount *)accounts->data;
7212 gchar *name, *from = NULL;
7214 if (ac == compose->account) def_menu = num;
7216 name = g_markup_printf_escaped(_("From: <i>%s</i>"),
7219 if (ac == compose->account) {
7220 if (ac->name && *ac->name) {
7222 QUOTE_IF_REQUIRED_NORMAL(buf, ac->name, return NULL);
7223 from = g_strdup_printf("%s <%s>",
7225 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7227 from = g_strdup_printf("%s",
7229 gtk_entry_set_text(GTK_ENTRY(from_name), from);
7232 COMBOBOX_ADD(menu, name, ac->account_id);
7237 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), def_menu);
7239 g_signal_connect(G_OBJECT(optmenu), "changed",
7240 G_CALLBACK(account_activated),
7242 g_signal_connect(G_OBJECT(from_name), "populate-popup",
7243 G_CALLBACK(compose_entry_popup_extend),
7246 gtk_box_pack_start(GTK_BOX(hbox), optmenubox, FALSE, FALSE, 0);
7247 gtk_box_pack_start(GTK_BOX(hbox), from_name, TRUE, TRUE, 0);
7249 CLAWS_SET_TIP(optmenubox,
7250 _("Account to use for this email"));
7251 CLAWS_SET_TIP(from_name,
7252 _("Sender address to be used"));
7254 compose->from_name = from_name;
7259 static void compose_set_priority_cb(gpointer data,
7263 Compose *compose = (Compose *) data;
7264 compose->priority = action;
7267 static void compose_reply_change_mode(gpointer data,
7271 Compose *compose = (Compose *) data;
7272 gboolean was_modified = compose->modified;
7274 gboolean all = FALSE, ml = FALSE, sender = FALSE, followup = FALSE;
7276 g_return_if_fail(compose->replyinfo != NULL);
7278 if (action == COMPOSE_REPLY && prefs_common.default_reply_list)
7280 if (action == COMPOSE_REPLY && compose->rmode == COMPOSE_FOLLOWUP_AND_REPLY_TO)
7282 if (action == COMPOSE_REPLY_TO_ALL)
7284 if (action == COMPOSE_REPLY_TO_SENDER)
7286 if (action == COMPOSE_REPLY_TO_LIST)
7289 compose_remove_header_entries(compose);
7290 compose_reply_set_entry(compose, compose->replyinfo, all, ml, sender, followup);
7291 if (compose->account->set_autocc && compose->account->auto_cc)
7292 compose_entry_append(compose, compose->account->auto_cc, COMPOSE_CC);
7294 if (compose->account->set_autobcc && compose->account->auto_bcc)
7295 compose_entry_append(compose, compose->account->auto_bcc, COMPOSE_BCC);
7297 if (compose->account->set_autoreplyto && compose->account->auto_replyto)
7298 compose_entry_append(compose, compose->account->auto_replyto, COMPOSE_REPLYTO);
7299 compose_show_first_last_header(compose, TRUE);
7300 compose->modified = was_modified;
7301 compose_set_title(compose);
7304 static void compose_update_priority_menu_item(Compose * compose)
7306 GtkItemFactory *ifactory;
7307 GtkWidget *menuitem = NULL;
7309 ifactory = gtk_item_factory_from_widget(compose->menubar);
7311 switch (compose->priority) {
7312 case PRIORITY_HIGHEST:
7313 menuitem = gtk_item_factory_get_item
7314 (ifactory, "/Options/Priority/Highest");
7317 menuitem = gtk_item_factory_get_item
7318 (ifactory, "/Options/Priority/High");
7320 case PRIORITY_NORMAL:
7321 menuitem = gtk_item_factory_get_item
7322 (ifactory, "/Options/Priority/Normal");
7325 menuitem = gtk_item_factory_get_item
7326 (ifactory, "/Options/Priority/Low");
7328 case PRIORITY_LOWEST:
7329 menuitem = gtk_item_factory_get_item
7330 (ifactory, "/Options/Priority/Lowest");
7333 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7336 static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
7338 Compose *compose = (Compose *) data;
7340 GtkItemFactory *ifactory;
7341 gboolean can_sign = FALSE, can_encrypt = FALSE;
7343 g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(widget));
7345 if (!GTK_CHECK_MENU_ITEM(widget)->active)
7348 systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
7349 g_free(compose->privacy_system);
7350 compose->privacy_system = NULL;
7351 if (systemid != NULL) {
7352 compose->privacy_system = g_strdup(systemid);
7354 can_sign = privacy_system_can_sign(systemid);
7355 can_encrypt = privacy_system_can_encrypt(systemid);
7358 debug_print("activated privacy system: %s\n", systemid != NULL ? systemid : "None");
7360 ifactory = gtk_item_factory_from_widget(compose->menubar);
7361 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7362 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7365 static void compose_update_privacy_system_menu_item(Compose * compose, gboolean warn)
7367 static gchar *branch_path = "/Options/Privacy System";
7368 GtkItemFactory *ifactory;
7369 GtkWidget *menuitem = NULL;
7371 gboolean can_sign = FALSE, can_encrypt = FALSE;
7372 gboolean found = FALSE;
7374 ifactory = gtk_item_factory_from_widget(compose->menubar);
7376 if (compose->privacy_system != NULL) {
7378 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7379 g_return_if_fail(menuitem != NULL);
7381 amenu = GTK_MENU_SHELL(menuitem)->children;
7383 while (amenu != NULL) {
7384 GList *alist = amenu->next;
7386 systemid = g_object_get_data(G_OBJECT(amenu->data), "privacy_system");
7387 if (systemid != NULL) {
7388 if (strcmp(systemid, compose->privacy_system) == 0) {
7389 menuitem = GTK_WIDGET(amenu->data);
7391 can_sign = privacy_system_can_sign(systemid);
7392 can_encrypt = privacy_system_can_encrypt(systemid);
7396 } else if (strlen(compose->privacy_system) == 0) {
7397 menuitem = GTK_WIDGET(amenu->data);
7400 can_encrypt = FALSE;
7407 if (menuitem != NULL)
7408 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
7410 if (warn && !found && strlen(compose->privacy_system)) {
7411 alertpanel_warning(_("The privacy system '%s' cannot be loaded. You "
7412 "will not be able to sign or encrypt this message."),
7413 compose->privacy_system);
7417 menu_set_sensitive(ifactory, "/Options/Sign", can_sign);
7418 menu_set_sensitive(ifactory, "/Options/Encrypt", can_encrypt);
7421 static void compose_set_out_encoding(Compose *compose)
7423 GtkItemFactoryEntry *entry;
7424 GtkItemFactory *ifactory;
7425 CharSet out_encoding;
7426 gchar *path, *p, *q;
7429 out_encoding = conv_get_charset_from_str(prefs_common.outgoing_charset);
7430 ifactory = gtk_item_factory_from_widget(compose->menubar);
7432 for (entry = compose_entries; entry->callback != compose_address_cb;
7434 if (entry->callback == compose_set_encoding_cb &&
7435 (CharSet)entry->callback_action == out_encoding) {
7436 p = q = path = g_strdup(entry->path);
7448 item = gtk_item_factory_get_item(ifactory, path);
7449 gtk_widget_activate(item);
7456 static void compose_set_template_menu(Compose *compose)
7458 GSList *tmpl_list, *cur;
7462 tmpl_list = template_get_config();
7464 menu = gtk_menu_new();
7466 for (cur = tmpl_list; cur != NULL; cur = cur->next) {
7467 Template *tmpl = (Template *)cur->data;
7469 item = gtk_menu_item_new_with_label(tmpl->name);
7470 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
7471 g_signal_connect(G_OBJECT(item), "activate",
7472 G_CALLBACK(compose_template_activate_cb),
7474 g_object_set_data(G_OBJECT(item), "template", tmpl);
7475 gtk_widget_show(item);
7478 gtk_widget_show(menu);
7479 gtk_menu_item_set_submenu(GTK_MENU_ITEM(compose->tmpl_menu), menu);
7482 void compose_update_actions_menu(Compose *compose)
7484 GtkItemFactory *ifactory;
7486 ifactory = gtk_item_factory_from_widget(compose->menubar);
7487 action_update_compose_menu(ifactory, "/Tools/Actions", compose);
7490 static void compose_update_privacy_systems_menu(Compose *compose)
7492 static gchar *branch_path = "/Options/Privacy System";
7493 GtkItemFactory *ifactory;
7494 GtkWidget *menuitem;
7495 GSList *systems, *cur;
7498 GtkWidget *system_none;
7501 ifactory = gtk_item_factory_from_widget(compose->menubar);
7503 /* remove old entries */
7504 menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
7505 g_return_if_fail(menuitem != NULL);
7507 amenu = GTK_MENU_SHELL(menuitem)->children->next;
7508 while (amenu != NULL) {
7509 GList *alist = amenu->next;
7510 gtk_widget_destroy(GTK_WIDGET(amenu->data));
7514 system_none = gtk_item_factory_get_widget(ifactory,
7515 "/Options/Privacy System/None");
7517 g_signal_connect(G_OBJECT(system_none), "activate",
7518 G_CALLBACK(compose_set_privacy_system_cb), compose);
7520 systems = privacy_get_system_ids();
7521 for (cur = systems; cur != NULL; cur = g_slist_next(cur)) {
7522 gchar *systemid = cur->data;
7524 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(system_none));
7525 widget = gtk_radio_menu_item_new_with_label(group,
7526 privacy_system_get_name(systemid));
7527 g_object_set_data_full(G_OBJECT(widget), "privacy_system",
7528 g_strdup(systemid), g_free);
7529 g_signal_connect(G_OBJECT(widget), "activate",
7530 G_CALLBACK(compose_set_privacy_system_cb), compose);
7532 gtk_menu_append(GTK_MENU(system_none->parent), widget);
7533 gtk_widget_show(widget);
7536 g_slist_free(systems);
7539 void compose_reflect_prefs_all(void)
7544 for (cur = compose_list; cur != NULL; cur = cur->next) {
7545 compose = (Compose *)cur->data;
7546 compose_set_template_menu(compose);
7550 void compose_reflect_prefs_pixmap_theme(void)
7555 for (cur = compose_list; cur != NULL; cur = cur->next) {
7556 compose = (Compose *)cur->data;
7557 toolbar_update(TOOLBAR_COMPOSE, compose);
7561 static const gchar *compose_quote_char_from_context(Compose *compose)
7563 const gchar *qmark = NULL;
7565 g_return_val_if_fail(compose != NULL, NULL);
7567 switch (compose->mode) {
7568 /* use forward-specific quote char */
7569 case COMPOSE_FORWARD:
7570 case COMPOSE_FORWARD_AS_ATTACH:
7571 case COMPOSE_FORWARD_INLINE:
7572 if (compose->folder && compose->folder->prefs &&
7573 compose->folder->prefs->forward_with_format)
7574 qmark = compose->folder->prefs->forward_quotemark;
7575 else if (compose->account->forward_with_format)
7576 qmark = compose->account->forward_quotemark;
7578 qmark = prefs_common.fw_quotemark;
7581 /* use reply-specific quote char in all other modes */
7583 if (compose->folder && compose->folder->prefs &&
7584 compose->folder->prefs->reply_with_format)
7585 qmark = compose->folder->prefs->reply_quotemark;
7586 else if (compose->account->reply_with_format)
7587 qmark = compose->account->reply_quotemark;
7589 qmark = prefs_common.quotemark;
7593 if (qmark == NULL || *qmark == '\0')
7599 static void compose_template_apply(Compose *compose, Template *tmpl,
7603 GtkTextBuffer *buffer;
7607 gchar *parsed_str = NULL;
7608 gint cursor_pos = 0;
7609 const gchar *err_msg = _("Template body format error at line %d.");
7612 /* process the body */
7614 text = GTK_TEXT_VIEW(compose->text);
7615 buffer = gtk_text_view_get_buffer(text);
7618 qmark = compose_quote_char_from_context(compose);
7620 if (compose->replyinfo != NULL) {
7623 gtk_text_buffer_set_text(buffer, "", -1);
7624 mark = gtk_text_buffer_get_insert(buffer);
7625 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7627 parsed_str = compose_quote_fmt(compose, compose->replyinfo,
7628 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7630 } else if (compose->fwdinfo != NULL) {
7633 gtk_text_buffer_set_text(buffer, "", -1);
7634 mark = gtk_text_buffer_get_insert(buffer);
7635 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7637 parsed_str = compose_quote_fmt(compose, compose->fwdinfo,
7638 tmpl->value, qmark, NULL, FALSE, FALSE, err_msg);
7641 MsgInfo* dummyinfo = compose_msginfo_new_from_compose(compose);
7643 GtkTextIter start, end;
7646 gtk_text_buffer_get_start_iter(buffer, &start);
7647 gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
7648 tmp = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
7650 /* clear the buffer now */
7652 gtk_text_buffer_set_text(buffer, "", -1);
7654 parsed_str = compose_quote_fmt(compose, dummyinfo,
7655 tmpl->value, qmark, tmp, FALSE, FALSE, err_msg);
7656 procmsg_msginfo_free( dummyinfo );
7662 gtk_text_buffer_set_text(buffer, "", -1);
7663 mark = gtk_text_buffer_get_insert(buffer);
7664 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
7667 if (replace && parsed_str && compose->account->auto_sig)
7668 compose_insert_sig(compose, FALSE);
7670 if (replace && parsed_str) {
7671 gtk_text_buffer_get_start_iter(buffer, &iter);
7672 gtk_text_buffer_place_cursor(buffer, &iter);
7676 cursor_pos = quote_fmt_get_cursor_pos();
7677 compose->set_cursor_pos = cursor_pos;
7678 if (cursor_pos == -1)
7680 gtk_text_buffer_get_start_iter(buffer, &iter);
7681 gtk_text_buffer_get_iter_at_offset(buffer, &iter, cursor_pos);
7682 gtk_text_buffer_place_cursor(buffer, &iter);
7685 /* process the other fields */
7687 compose_template_apply_fields(compose, tmpl);
7688 compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
7689 quote_fmt_reset_vartable();
7690 compose_changed_cb(NULL, compose);
7693 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
7695 MsgInfo* dummyinfo = NULL;
7696 MsgInfo *msginfo = NULL;
7699 if (compose->replyinfo != NULL)
7700 msginfo = compose->replyinfo;
7701 else if (compose->fwdinfo != NULL)
7702 msginfo = compose->fwdinfo;
7704 dummyinfo = compose_msginfo_new_from_compose(compose);
7705 msginfo = dummyinfo;
7708 if (tmpl->from && *tmpl->from != '\0') {
7710 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7711 compose->gtkaspell);
7713 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7715 quote_fmt_scan_string(tmpl->from);
7718 buf = quote_fmt_get_buffer();
7720 alertpanel_error(_("Template From format error."));
7722 gtk_entry_set_text(GTK_ENTRY(compose->from_name), buf);
7726 if (tmpl->to && *tmpl->to != '\0') {
7728 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7729 compose->gtkaspell);
7731 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7733 quote_fmt_scan_string(tmpl->to);
7736 buf = quote_fmt_get_buffer();
7738 alertpanel_error(_("Template To format error."));
7740 compose_entry_append(compose, buf, COMPOSE_TO);
7744 if (tmpl->cc && *tmpl->cc != '\0') {
7746 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7747 compose->gtkaspell);
7749 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7751 quote_fmt_scan_string(tmpl->cc);
7754 buf = quote_fmt_get_buffer();
7756 alertpanel_error(_("Template Cc format error."));
7758 compose_entry_append(compose, buf, COMPOSE_CC);
7762 if (tmpl->bcc && *tmpl->bcc != '\0') {
7764 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7765 compose->gtkaspell);
7767 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7769 quote_fmt_scan_string(tmpl->bcc);
7772 buf = quote_fmt_get_buffer();
7774 alertpanel_error(_("Template Bcc format error."));
7776 compose_entry_append(compose, buf, COMPOSE_BCC);
7780 /* process the subject */
7781 if (tmpl->subject && *tmpl->subject != '\0') {
7783 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE,
7784 compose->gtkaspell);
7786 quote_fmt_init(msginfo, NULL, NULL, FALSE, compose->account, FALSE);
7788 quote_fmt_scan_string(tmpl->subject);
7791 buf = quote_fmt_get_buffer();
7793 alertpanel_error(_("Template subject format error."));
7795 gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);
7799 procmsg_msginfo_free( dummyinfo );
7802 static void compose_destroy(Compose *compose)
7804 GtkTextBuffer *buffer;
7805 GtkClipboard *clipboard;
7807 compose_list = g_list_remove(compose_list, compose);
7809 if (compose->updating) {
7810 debug_print("danger, not destroying anything now\n");
7811 compose->deferred_destroy = TRUE;
7814 /* NOTE: address_completion_end() does nothing with the window
7815 * however this may change. */
7816 address_completion_end(compose->window);
7818 slist_free_strings(compose->to_list);
7819 g_slist_free(compose->to_list);
7820 slist_free_strings(compose->newsgroup_list);
7821 g_slist_free(compose->newsgroup_list);
7822 slist_free_strings(compose->header_list);
7823 g_slist_free(compose->header_list);
7825 procmsg_msginfo_free(compose->targetinfo);
7826 procmsg_msginfo_free(compose->replyinfo);
7827 procmsg_msginfo_free(compose->fwdinfo);
7829 g_free(compose->replyto);
7830 g_free(compose->cc);
7831 g_free(compose->bcc);
7832 g_free(compose->newsgroups);
7833 g_free(compose->followup_to);
7835 g_free(compose->ml_post);
7837 g_free(compose->inreplyto);
7838 g_free(compose->references);
7839 g_free(compose->msgid);
7840 g_free(compose->boundary);
7842 g_free(compose->redirect_filename);
7843 if (compose->undostruct)
7844 undo_destroy(compose->undostruct);
7846 g_free(compose->sig_str);
7848 g_free(compose->exteditor_file);
7850 g_free(compose->orig_charset);
7852 g_free(compose->privacy_system);
7854 if (addressbook_get_target_compose() == compose)
7855 addressbook_set_target_compose(NULL);
7858 if (compose->gtkaspell) {
7859 gtkaspell_delete(compose->gtkaspell);
7860 compose->gtkaspell = NULL;
7864 prefs_common.compose_width = compose->scrolledwin->allocation.width;
7865 prefs_common.compose_height = compose->window->allocation.height;
7867 if (!gtk_widget_get_parent(compose->paned))
7868 gtk_widget_destroy(compose->paned);
7869 gtk_widget_destroy(compose->popupmenu);
7871 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
7872 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
7873 gtk_text_buffer_remove_selection_clipboard(buffer, clipboard);
7875 gtk_widget_destroy(compose->window);
7876 toolbar_destroy(compose->toolbar);
7877 g_free(compose->toolbar);
7878 g_mutex_free(compose->mutex);
7882 static void compose_attach_info_free(AttachInfo *ainfo)
7884 g_free(ainfo->file);
7885 g_free(ainfo->content_type);
7886 g_free(ainfo->name);
7890 static void compose_attach_update_label(Compose *compose)
7895 GtkTreeModel *model;
7900 model = gtk_tree_view_get_model(GTK_TREE_VIEW(compose->attach_clist));
7901 if(!gtk_tree_model_get_iter_first(model, &iter)) {
7902 gtk_label_set_text(GTK_LABEL(compose->attach_label), "");
7906 while(gtk_tree_model_iter_next(model, &iter))
7909 text = g_strdup_printf("(%d)", i);
7910 gtk_label_set_text(GTK_LABEL(compose->attach_label), text);
7914 static void compose_attach_remove_selected(Compose *compose)
7916 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7917 GtkTreeSelection *selection;
7919 GtkTreeModel *model;
7921 selection = gtk_tree_view_get_selection(tree_view);
7922 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7927 for (cur = sel; cur != NULL; cur = cur->next) {
7928 GtkTreePath *path = cur->data;
7929 GtkTreeRowReference *ref = gtk_tree_row_reference_new
7932 gtk_tree_path_free(path);
7935 for (cur = sel; cur != NULL; cur = cur->next) {
7936 GtkTreeRowReference *ref = cur->data;
7937 GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
7940 if (gtk_tree_model_get_iter(model, &iter, path))
7941 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
7943 gtk_tree_path_free(path);
7944 gtk_tree_row_reference_free(ref);
7948 compose_attach_update_label(compose);
7951 static struct _AttachProperty
7954 GtkWidget *mimetype_entry;
7955 GtkWidget *encoding_optmenu;
7956 GtkWidget *path_entry;
7957 GtkWidget *filename_entry;
7959 GtkWidget *cancel_btn;
7962 static void gtk_tree_path_free_(gpointer ptr, gpointer data)
7964 gtk_tree_path_free((GtkTreePath *)ptr);
7967 static void compose_attach_property(Compose *compose)
7969 GtkTreeView *tree_view = GTK_TREE_VIEW(compose->attach_clist);
7971 GtkComboBox *optmenu;
7972 GtkTreeSelection *selection;
7974 GtkTreeModel *model;
7977 static gboolean cancelled;
7979 /* only if one selected */
7980 selection = gtk_tree_view_get_selection(tree_view);
7981 if (gtk_tree_selection_count_selected_rows(selection) != 1)
7984 sel = gtk_tree_selection_get_selected_rows(selection, &model);
7988 path = (GtkTreePath *) sel->data;
7989 gtk_tree_model_get_iter(model, &iter, path);
7990 gtk_tree_model_get(model, &iter, COL_DATA, &ainfo, -1);
7993 g_list_foreach(sel, gtk_tree_path_free_, NULL);
7999 if (!attach_prop.window)
8000 compose_attach_property_create(&cancelled);
8001 gtk_widget_grab_focus(attach_prop.ok_btn);
8002 gtk_widget_show(attach_prop.window);
8003 manage_window_set_transient(GTK_WINDOW(attach_prop.window));
8005 optmenu = GTK_COMBO_BOX(attach_prop.encoding_optmenu);
8006 if (ainfo->encoding == ENC_UNKNOWN)
8007 combobox_select_by_data(optmenu, ENC_BASE64);
8009 combobox_select_by_data(optmenu, ainfo->encoding);
8011 gtk_entry_set_text(GTK_ENTRY(attach_prop.mimetype_entry),
8012 ainfo->content_type ? ainfo->content_type : "");
8013 gtk_entry_set_text(GTK_ENTRY(attach_prop.path_entry),
8014 ainfo->file ? ainfo->file : "");
8015 gtk_entry_set_text(GTK_ENTRY(attach_prop.filename_entry),
8016 ainfo->name ? ainfo->name : "");
8019 const gchar *entry_text;
8021 gchar *cnttype = NULL;
8028 gtk_widget_hide(attach_prop.window);
8033 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.mimetype_entry));
8034 if (*entry_text != '\0') {
8037 text = g_strstrip(g_strdup(entry_text));
8038 if ((p = strchr(text, '/')) && !strchr(p + 1, '/')) {
8039 cnttype = g_strdup(text);
8042 alertpanel_error(_("Invalid MIME type."));
8048 ainfo->encoding = combobox_get_active_data(optmenu);
8050 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.path_entry));
8051 if (*entry_text != '\0') {
8052 if (is_file_exist(entry_text) &&
8053 (size = get_file_size(entry_text)) > 0)
8054 file = g_strdup(entry_text);
8057 (_("File doesn't exist or is empty."));
8063 entry_text = gtk_entry_get_text(GTK_ENTRY(attach_prop.filename_entry));
8064 if (*entry_text != '\0') {
8065 g_free(ainfo->name);
8066 ainfo->name = g_strdup(entry_text);
8070 g_free(ainfo->content_type);
8071 ainfo->content_type = cnttype;
8074 g_free(ainfo->file);
8078 ainfo->size = (goffset)size;
8080 /* update tree store */
8081 text = to_human_readable(ainfo->size);
8082 gtk_tree_model_get_iter(model, &iter, path);
8083 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
8084 COL_MIMETYPE, ainfo->content_type,
8086 COL_NAME, ainfo->name,
8092 gtk_tree_path_free(path);
8095 #define SET_LABEL_AND_ENTRY(str, entry, top) \
8097 label = gtk_label_new(str); \
8098 gtk_table_attach(GTK_TABLE(table), label, 0, 1, top, (top + 1), \
8099 GTK_FILL, 0, 0, 0); \
8100 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); \
8102 entry = gtk_entry_new(); \
8103 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, top, (top + 1), \
8104 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0); \
8107 static void compose_attach_property_create(gboolean *cancelled)
8113 GtkWidget *mimetype_entry;
8116 GtkListStore *optmenu_menu;
8117 GtkWidget *path_entry;
8118 GtkWidget *filename_entry;
8121 GtkWidget *cancel_btn;
8122 GList *mime_type_list, *strlist;
8125 debug_print("Creating attach_property window...\n");
8127 window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "compose_attach_property");
8128 gtk_widget_set_size_request(window, 480, -1);
8129 gtk_container_set_border_width(GTK_CONTAINER(window), 8);
8130 gtk_window_set_title(GTK_WINDOW(window), _("Properties"));
8131 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
8132 gtk_window_set_modal(GTK_WINDOW(window), TRUE);
8133 g_signal_connect(G_OBJECT(window), "delete_event",
8134 G_CALLBACK(attach_property_delete_event),
8136 g_signal_connect(G_OBJECT(window), "key_press_event",
8137 G_CALLBACK(attach_property_key_pressed),
8140 vbox = gtk_vbox_new(FALSE, 8);
8141 gtk_container_add(GTK_CONTAINER(window), vbox);
8143 table = gtk_table_new(4, 2, FALSE);
8144 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
8145 gtk_table_set_row_spacings(GTK_TABLE(table), 8);
8146 gtk_table_set_col_spacings(GTK_TABLE(table), 8);
8148 label = gtk_label_new(_("MIME type"));
8149 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, (0 + 1),
8151 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8152 mimetype_entry = gtk_combo_box_entry_new_text();
8153 gtk_table_attach(GTK_TABLE(table), mimetype_entry, 1, 2, 0, (0 + 1),
8154 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8156 /* stuff with list */
8157 mime_type_list = procmime_get_mime_type_list();
8159 for (; mime_type_list != NULL; mime_type_list = mime_type_list->next) {
8160 MimeType *type = (MimeType *) mime_type_list->data;
8163 tmp = g_strdup_printf("%s/%s", type->type, type->sub_type);
8165 if (g_list_find_custom(strlist, tmp, (GCompareFunc)strcmp2))
8168 strlist = g_list_insert_sorted(strlist, (gpointer)tmp,
8169 (GCompareFunc)strcmp2);
8172 for (mime_type_list = strlist; mime_type_list != NULL;
8173 mime_type_list = mime_type_list->next) {
8174 gtk_combo_box_append_text(GTK_COMBO_BOX(mimetype_entry), mime_type_list->data);
8175 g_free(mime_type_list->data);
8177 g_list_free(strlist);
8178 gtk_combo_box_set_active(GTK_COMBO_BOX(mimetype_entry), 0);
8179 mimetype_entry = GTK_BIN(mimetype_entry)->child;
8181 label = gtk_label_new(_("Encoding"));
8182 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
8184 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
8186 hbox = gtk_hbox_new(FALSE, 0);
8187 gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 1, 2,
8188 GTK_EXPAND|GTK_SHRINK|GTK_FILL, 0, 0, 0);
8190 optmenu = gtkut_sc_combobox_create(NULL, TRUE);
8191 optmenu_menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(optmenu)));
8193 COMBOBOX_ADD(optmenu_menu, "7bit", ENC_7BIT);
8194 COMBOBOX_ADD(optmenu_menu, "8bit", ENC_8BIT);
8195 COMBOBOX_ADD(optmenu_menu, "quoted-printable", ENC_QUOTED_PRINTABLE);
8196 COMBOBOX_ADD(optmenu_menu, "base64", ENC_BASE64);
8197 gtk_combo_box_set_active(GTK_COMBO_BOX(optmenu), 0);
8199 gtk_box_pack_start(GTK_BOX(hbox), optmenu, TRUE, TRUE, 0);
8201 SET_LABEL_AND_ENTRY(_("Path"), path_entry, 2);
8202 SET_LABEL_AND_ENTRY(_("File name"), filename_entry, 3);
8204 gtkut_stock_button_set_create(&hbbox, &cancel_btn, GTK_STOCK_CANCEL,
8205 &ok_btn, GTK_STOCK_OK,
8207 gtk_box_pack_end(GTK_BOX(vbox), hbbox, FALSE, FALSE, 0);
8208 gtk_widget_grab_default(ok_btn);
8210 g_signal_connect(G_OBJECT(ok_btn), "clicked",
8211 G_CALLBACK(attach_property_ok),
8213 g_signal_connect(G_OBJECT(cancel_btn), "clicked",
8214 G_CALLBACK(attach_property_cancel),
8217 gtk_widget_show_all(vbox);
8219 attach_prop.window = window;
8220 attach_prop.mimetype_entry = mimetype_entry;
8221 attach_prop.encoding_optmenu = optmenu;
8222 attach_prop.path_entry = path_entry;
8223 attach_prop.filename_entry = filename_entry;
8224 attach_prop.ok_btn = ok_btn;
8225 attach_prop.cancel_btn = cancel_btn;
8228 #undef SET_LABEL_AND_ENTRY
8230 static void attach_property_ok(GtkWidget *widget, gboolean *cancelled)
8236 static void attach_property_cancel(GtkWidget *widget, gboolean *cancelled)
8242 static gint attach_property_delete_event(GtkWidget *widget, GdkEventAny *event,
8243 gboolean *cancelled)
8251 static gboolean attach_property_key_pressed(GtkWidget *widget,
8253 gboolean *cancelled)
8255 if (event && event->keyval == GDK_Escape) {
8262 static void compose_exec_ext_editor(Compose *compose)
8269 tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
8270 G_DIR_SEPARATOR, compose);
8272 if (pipe(pipe_fds) < 0) {
8278 if ((pid = fork()) < 0) {
8285 /* close the write side of the pipe */
8288 compose->exteditor_file = g_strdup(tmp);
8289 compose->exteditor_pid = pid;
8291 compose_set_ext_editor_sensitive(compose, FALSE);
8293 compose->exteditor_ch = g_io_channel_unix_new(pipe_fds[0]);
8294 compose->exteditor_tag = g_io_add_watch(compose->exteditor_ch,
8298 } else { /* process-monitoring process */
8304 /* close the read side of the pipe */
8307 if (compose_write_body_to_file(compose, tmp) < 0) {
8308 fd_write_all(pipe_fds[1], "2\n", 2);
8312 pid_ed = compose_exec_ext_editor_real(tmp);
8314 fd_write_all(pipe_fds[1], "1\n", 2);
8318 /* wait until editor is terminated */
8319 waitpid(pid_ed, NULL, 0);
8321 fd_write_all(pipe_fds[1], "0\n", 2);
8328 #endif /* G_OS_UNIX */
8332 static gint compose_exec_ext_editor_real(const gchar *file)
8339 g_return_val_if_fail(file != NULL, -1);
8341 if ((pid = fork()) < 0) {
8346 if (pid != 0) return pid;
8348 /* grandchild process */
8350 if (setpgid(0, getppid()))
8353 if (prefs_common_get_ext_editor_cmd() &&
8354 (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
8355 *(p + 1) == 's' && !strchr(p + 2, '%')) {
8356 g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
8358 if (prefs_common_get_ext_editor_cmd())
8359 g_warning("External editor command line is invalid: '%s'\n",
8360 prefs_common_get_ext_editor_cmd());
8361 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
8364 cmdline = strsplit_with_quote(buf, " ", 1024);
8365 execvp(cmdline[0], cmdline);
8368 g_strfreev(cmdline);
8373 static gboolean compose_ext_editor_kill(Compose *compose)
8375 pid_t pgid = compose->exteditor_pid * -1;
8378 ret = kill(pgid, 0);
8380 if (ret == 0 || (ret == -1 && EPERM == errno)) {
8384 msg = g_strdup_printf
8385 (_("The external editor is still working.\n"
8386 "Force terminating the process?\n"
8387 "process group id: %d"), -pgid);
8388 val = alertpanel_full(_("Notice"), msg, GTK_STOCK_NO, GTK_STOCK_YES,
8389 NULL, FALSE, NULL, ALERT_WARNING, G_ALERTDEFAULT);
8393 if (val == G_ALERTALTERNATE) {
8394 g_source_remove(compose->exteditor_tag);
8395 g_io_channel_shutdown(compose->exteditor_ch,
8397 g_io_channel_unref(compose->exteditor_ch);
8399 if (kill(pgid, SIGTERM) < 0) perror("kill");
8400 waitpid(compose->exteditor_pid, NULL, 0);
8402 g_warning("Terminated process group id: %d", -pgid);
8403 g_warning("Temporary file: %s",
8404 compose->exteditor_file);
8406 compose_set_ext_editor_sensitive(compose, TRUE);
8408 g_free(compose->exteditor_file);
8409 compose->exteditor_file = NULL;
8410 compose->exteditor_pid = -1;
8411 compose->exteditor_ch = NULL;
8412 compose->exteditor_tag = -1;
8420 static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
8424 Compose *compose = (Compose *)data;
8427 debug_print(_("Compose: input from monitoring process\n"));
8429 g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
8431 g_io_channel_shutdown(source, FALSE, NULL);
8432 g_io_channel_unref(source);
8434 waitpid(compose->exteditor_pid, NULL, 0);
8436 if (buf[0] == '0') { /* success */
8437 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
8438 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
8440 gtk_text_buffer_set_text(buffer, "", -1);
8441 compose_insert_file(compose, compose->exteditor_file);
8442 compose_changed_cb(NULL, compose);
8444 if (claws_unlink(compose->exteditor_file) < 0)
8445 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8446 } else if (buf[0] == '1') { /* failed */
8447 g_warning("Couldn't exec external editor\n");
8448 if (claws_unlink(compose->exteditor_file) < 0)
8449 FILE_OP_ERROR(compose->exteditor_file, "unlink");
8450 } else if (buf[0] == '2') {
8451 g_warning("Couldn't write to file\n");
8452 } else if (buf[0] == '3') {
8453 g_warning("Pipe read failed\n");
8456 compose_set_ext_editor_sensitive(compose, TRUE);
8458 g_free(compose->exteditor_file);
8459 compose->exteditor_file = NULL;
8460 compose->exteditor_pid = -1;
8461 compose->exteditor_ch = NULL;
8462 compose->exteditor_tag = -1;
8467 static void compose_set_ext_editor_sensitive(Compose *compose,
8470 GtkItemFactory *ifactory;
8472 ifactory = gtk_item_factory_from_widget(compose->menubar);
8474 menu_set_sensitive(ifactory, "/Message/Send", sensitive);
8475 menu_set_sensitive(ifactory, "/Message/Send later", sensitive);
8476 menu_set_sensitive(ifactory, "/Message/Insert file", sensitive);
8477 menu_set_sensitive(ifactory, "/Message/Insert signature", sensitive);
8478 menu_set_sensitive(ifactory, "/Edit/Wrap current paragraph", sensitive);
8479 menu_set_sensitive(ifactory, "/Edit/Wrap all long lines", sensitive);
8480 menu_set_sensitive(ifactory, "/Edit/Edit with external editor",
8483 gtk_widget_set_sensitive(compose->text, sensitive);
8484 if (compose->toolbar->send_btn)
8485 gtk_widget_set_sensitive(compose->toolbar->send_btn, sensitive);
8486 if (compose->toolbar->sendl_btn)
8487 gtk_widget_set_sensitive(compose->toolbar->sendl_btn, sensitive);
8488 if (compose->toolbar->draft_btn)
8489 gtk_widget_set_sensitive(compose->toolbar->draft_btn, sensitive);
8490 if (compose->toolbar->insert_btn)
8491 gtk_widget_set_sensitive(compose->toolbar->insert_btn, sensitive);
8492 if (compose->toolbar->sig_btn)
8493 gtk_widget_set_sensitive(compose->toolbar->sig_btn, sensitive);
8494 if (compose->toolbar->exteditor_btn)
8495 gtk_widget_set_sensitive(compose->toolbar->exteditor_btn, sensitive);
8496 if (compose->toolbar->linewrap_current_btn)
8497 gtk_widget_set_sensitive(compose->toolbar->linewrap_current_btn, sensitive);
8498 if (compose->toolbar->linewrap_all_btn)
8499 gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
8501 #endif /* G_OS_UNIX */
8504 * compose_undo_state_changed:
8506 * Change the sensivity of the menuentries undo and redo
8508 static void compose_undo_state_changed(UndoMain *undostruct, gint undo_state,
8509 gint redo_state, gpointer data)
8511 GtkWidget *widget = GTK_WIDGET(data);
8512 GtkItemFactory *ifactory;
8514 g_return_if_fail(widget != NULL);
8516 ifactory = gtk_item_factory_from_widget(widget);
8518 switch (undo_state) {
8519 case UNDO_STATE_TRUE:
8520 if (!undostruct->undo_state) {
8521 undostruct->undo_state = TRUE;
8522 menu_set_sensitive(ifactory, "/Edit/Undo", TRUE);
8525 case UNDO_STATE_FALSE:
8526 if (undostruct->undo_state) {
8527 undostruct->undo_state = FALSE;
8528 menu_set_sensitive(ifactory, "/Edit/Undo", FALSE);
8531 case UNDO_STATE_UNCHANGED:
8533 case UNDO_STATE_REFRESH:
8534 menu_set_sensitive(ifactory, "/Edit/Undo",
8535 undostruct->undo_state);
8538 g_warning("Undo state not recognized");
8542 switch (redo_state) {
8543 case UNDO_STATE_TRUE:
8544 if (!undostruct->redo_state) {
8545 undostruct->redo_state = TRUE;
8546 menu_set_sensitive(ifactory, "/Edit/Redo", TRUE);
8549 case UNDO_STATE_FALSE:
8550 if (undostruct->redo_state) {
8551 undostruct->redo_state = FALSE;
8552 menu_set_sensitive(ifactory, "/Edit/Redo", FALSE);
8555 case UNDO_STATE_UNCHANGED:
8557 case UNDO_STATE_REFRESH:
8558 menu_set_sensitive(ifactory, "/Edit/Redo",
8559 undostruct->redo_state);
8562 g_warning("Redo state not recognized");
8567 /* callback functions */
8569 /* compose_edit_size_alloc() - called when resized. don't know whether Gtk
8570 * includes "non-client" (windows-izm) in calculation, so this calculation
8571 * may not be accurate.
8573 static gboolean compose_edit_size_alloc(GtkEditable *widget,
8574 GtkAllocation *allocation,
8575 GtkSHRuler *shruler)
8577 if (prefs_common.show_ruler) {
8578 gint char_width = 0, char_height = 0;
8579 gint line_width_in_chars;
8581 gtkut_get_font_size(GTK_WIDGET(widget),
8582 &char_width, &char_height);
8583 line_width_in_chars =
8584 (allocation->width - allocation->x) / char_width;
8586 /* got the maximum */
8587 gtk_ruler_set_range(GTK_RULER(shruler),
8588 0.0, line_width_in_chars, 0,
8589 /*line_width_in_chars*/ char_width);
8595 static void account_activated(GtkComboBox *optmenu, gpointer data)
8597 Compose *compose = (Compose *)data;
8600 gchar *folderidentifier;
8601 gint account_id = 0;
8605 /* Get ID of active account in the combo box */
8606 menu = gtk_combo_box_get_model(optmenu);
8607 gtk_combo_box_get_active_iter(optmenu, &iter);
8608 gtk_tree_model_get(menu, &iter, 1, &account_id, -1);
8610 ac = account_find_from_id(account_id);
8611 g_return_if_fail(ac != NULL);
8613 if (ac != compose->account)
8614 compose_select_account(compose, ac, FALSE);
8616 /* Set message save folder */
8617 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8618 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn), prefs_common.savemsg);
8620 g_signal_connect(G_OBJECT(compose->savemsg_checkbtn), "toggled",
8621 G_CALLBACK(compose_savemsg_checkbtn_cb), compose);
8623 gtk_editable_delete_text(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8624 if (account_get_special_folder(compose->account, F_OUTBOX)) {
8625 folderidentifier = folder_item_get_identifier(account_get_special_folder
8626 (compose->account, F_OUTBOX));
8627 gtk_entry_set_text(GTK_ENTRY(compose->savemsg_entry), folderidentifier);
8628 g_free(folderidentifier);
8632 static void attach_selected(GtkTreeView *tree_view, GtkTreePath *tree_path,
8633 GtkTreeViewColumn *column, Compose *compose)
8635 compose_attach_property(compose);
8638 static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
8641 Compose *compose = (Compose *)data;
8642 GtkTreeSelection *attach_selection;
8643 gint attach_nr_selected;
8644 GtkItemFactory *ifactory;
8646 if (!event) return FALSE;
8648 if (event->button == 3) {
8649 attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
8650 attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
8651 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
8653 if (attach_nr_selected > 0)
8655 menu_set_sensitive(ifactory, "/Remove", TRUE);
8656 menu_set_sensitive(ifactory, "/Properties...", TRUE);
8658 menu_set_sensitive(ifactory, "/Remove", FALSE);
8659 menu_set_sensitive(ifactory, "/Properties...", FALSE);
8662 gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
8663 NULL, NULL, event->button, event->time);
8670 static gboolean attach_key_pressed(GtkWidget *widget, GdkEventKey *event,
8673 Compose *compose = (Compose *)data;
8675 if (!event) return FALSE;
8677 switch (event->keyval) {
8679 compose_attach_remove_selected(compose);
8685 static void compose_allow_user_actions (Compose *compose, gboolean allow)
8687 GtkItemFactory *ifactory = gtk_item_factory_from_widget(compose->menubar);
8688 toolbar_comp_set_sensitive(compose, allow);
8689 menu_set_sensitive(ifactory, "/Message", allow);
8690 menu_set_sensitive(ifactory, "/Edit", allow);
8692 menu_set_sensitive(ifactory, "/Spelling", allow);
8694 menu_set_sensitive(ifactory, "/Options", allow);
8695 menu_set_sensitive(ifactory, "/Tools", allow);
8696 menu_set_sensitive(ifactory, "/Help", allow);
8698 gtk_text_view_set_editable(GTK_TEXT_VIEW(compose->text), allow);
8702 static void compose_send_cb(gpointer data, guint action, GtkWidget *widget)
8704 Compose *compose = (Compose *)data;
8706 if (prefs_common.work_offline &&
8707 !inc_offline_should_override(TRUE,
8708 _("Claws Mail needs network access in order "
8709 "to send this email.")))
8712 if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
8713 g_source_remove(compose->draft_timeout_tag);
8714 compose->draft_timeout_tag = -1;
8717 compose_send(compose);
8720 static void compose_send_later_cb(gpointer data, guint action,
8723 Compose *compose = (Compose *)data;
8727 val = compose_queue_sub(compose, NULL, NULL, NULL, TRUE, TRUE);
8731 compose_close(compose);
8732 } else if (val == -1) {
8733 alertpanel_error(_("Could not queue message."));
8734 } else if (val == -2) {
8735 alertpanel_error(_("Could not queue message:\n\n%s."), strerror(errno));
8736 } else if (val == -3) {
8737 if (privacy_peek_error())
8738 alertpanel_error(_("Could not queue message for sending:\n\n"
8739 "Signature failed: %s"), privacy_get_error());
8740 } else if (val == -4) {
8741 alertpanel_error(_("Could not queue message for sending:\n\n"
8742 "Charset conversion failed."));
8743 } else if (val == -5) {
8744 alertpanel_error(_("Could not queue message for sending:\n\n"
8745 "Couldn't get recipient encryption key."));
8746 } else if (val == -6) {
8749 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
8752 #define DRAFTED_AT_EXIT "drafted_at_exit"
8753 static void compose_register_draft(MsgInfo *info)
8755 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8756 DRAFTED_AT_EXIT, NULL);
8757 FILE *fp = fopen(filepath, "ab");
8760 fprintf(fp, "%s\t%d\n", folder_item_get_identifier(info->folder),
8768 gboolean compose_draft (gpointer data, guint action)
8770 Compose *compose = (Compose *)data;
8774 MsgFlags flag = {0, 0};
8775 static gboolean lock = FALSE;
8776 MsgInfo *newmsginfo;
8778 gboolean target_locked = FALSE;
8779 gboolean err = FALSE;
8781 if (lock) return FALSE;
8783 if (compose->sending)
8786 draft = account_get_special_folder(compose->account, F_DRAFT);
8787 g_return_val_if_fail(draft != NULL, FALSE);
8789 if (!g_mutex_trylock(compose->mutex)) {
8790 /* we don't want to lock the mutex once it's available,
8791 * because as the only other part of compose.c locking
8792 * it is compose_close - which means once unlocked,
8793 * the compose struct will be freed */
8794 debug_print("couldn't lock mutex, probably sending\n");
8800 tmp = g_strdup_printf("%s%cdraft.%p", get_tmp_dir(),
8801 G_DIR_SEPARATOR, compose);
8802 if ((fp = g_fopen(tmp, "wb")) == NULL) {
8803 FILE_OP_ERROR(tmp, "fopen");
8807 /* chmod for security */
8808 if (change_file_mode_rw(fp, tmp) < 0) {
8809 FILE_OP_ERROR(tmp, "chmod");
8810 g_warning("can't change file mode\n");
8813 /* Save draft infos */
8814 err |= (fprintf(fp, "X-Claws-Account-Id:%d\n", compose->account->account_id) < 0);
8815 err |= (fprintf(fp, "S:%s\n", compose->account->address) < 0);
8817 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compose->savemsg_checkbtn))) {
8818 gchar *savefolderid;
8820 savefolderid = gtk_editable_get_chars(GTK_EDITABLE(compose->savemsg_entry), 0, -1);
8821 err |= (fprintf(fp, "SCF:%s\n", savefolderid) < 0);
8822 g_free(savefolderid);
8824 if (compose->return_receipt) {
8825 err |= (fprintf(fp, "RRCPT:1\n") < 0);
8827 if (compose->privacy_system) {
8828 err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
8829 err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
8830 err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
8833 /* Message-ID of message replying to */
8834 if ((compose->replyinfo != NULL) && (compose->replyinfo->msgid != NULL)) {
8837 folderid = folder_item_get_identifier(compose->replyinfo->folder);
8838 err |= (fprintf(fp, "RMID:%s\t%d\t%s\n", folderid, compose->replyinfo->msgnum, compose->replyinfo->msgid) < 0);
8841 /* Message-ID of message forwarding to */
8842 if ((compose->fwdinfo != NULL) && (compose->fwdinfo->msgid != NULL)) {
8845 folderid = folder_item_get_identifier(compose->fwdinfo->folder);
8846 err |= (fprintf(fp, "FMID:%s\t%d\t%s\n", folderid, compose->fwdinfo->msgnum, compose->fwdinfo->msgid) < 0);
8850 /* end of headers */
8851 err |= (fprintf(fp, "X-Claws-End-Special-Headers: 1\n") < 0);
8858 if (compose_write_to_file(compose, fp, COMPOSE_WRITE_FOR_STORE, action != COMPOSE_AUTO_SAVE) < 0) {
8862 if (fclose(fp) == EOF) {
8866 if (compose->targetinfo) {
8867 target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
8868 flag.perm_flags = target_locked?MSG_LOCKED:0;
8870 flag.tmp_flags = MSG_DRAFT;
8872 folder_item_scan(draft);
8873 if ((msgnum = folder_item_add_msg(draft, tmp, &flag, TRUE)) < 0) {
8874 MsgInfo *tmpinfo = NULL;
8875 debug_print("didn't get msgnum after adding draft [%s]\n", compose->msgid?compose->msgid:"no msgid");
8876 if (compose->msgid) {
8877 tmpinfo = folder_item_get_msginfo_by_msgid(draft, compose->msgid);
8880 msgnum = tmpinfo->msgnum;
8881 procmsg_msginfo_free(tmpinfo);
8882 debug_print("got draft msgnum %d from scanning\n", msgnum);
8884 debug_print("didn't get draft msgnum after scanning\n");
8887 debug_print("got draft msgnum %d from adding\n", msgnum);
8893 if (action != COMPOSE_AUTO_SAVE) {
8894 if (action != COMPOSE_DRAFT_FOR_EXIT)
8895 alertpanel_error(_("Could not save draft."));
8898 gtkut_window_popup(compose->window);
8899 val = alertpanel_full(_("Could not save draft"),
8900 _("Could not save draft.\n"
8901 "Do you want to cancel exit or discard this email?"),
8902 _("_Cancel exit"), _("_Discard email"), NULL,
8903 FALSE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
8904 if (val == G_ALERTALTERNATE) {
8906 g_mutex_unlock(compose->mutex); /* must be done before closing */
8907 compose_close(compose);
8911 g_mutex_unlock(compose->mutex); /* must be done before closing */
8920 if (compose->mode == COMPOSE_REEDIT) {
8921 compose_remove_reedit_target(compose, TRUE);
8924 newmsginfo = folder_item_get_msginfo(draft, msgnum);
8927 procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
8929 procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
8931 procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
8932 if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
8933 procmsg_msginfo_set_flags(newmsginfo, 0,
8934 MSG_HAS_ATTACHMENT);
8936 if (action == COMPOSE_DRAFT_FOR_EXIT) {
8937 compose_register_draft(newmsginfo);
8939 procmsg_msginfo_free(newmsginfo);
8942 folder_item_scan(draft);
8944 if (action == COMPOSE_QUIT_EDITING || action == COMPOSE_DRAFT_FOR_EXIT) {
8946 g_mutex_unlock(compose->mutex); /* must be done before closing */
8947 compose_close(compose);
8953 path = folder_item_fetch_msg(draft, msgnum);
8955 debug_print("can't fetch %s:%d\n", draft->path, msgnum);
8958 if (g_stat(path, &s) < 0) {
8959 FILE_OP_ERROR(path, "stat");
8965 procmsg_msginfo_free(compose->targetinfo);
8966 compose->targetinfo = procmsg_msginfo_new();
8967 compose->targetinfo->msgnum = msgnum;
8968 compose->targetinfo->size = (goffset)s.st_size;
8969 compose->targetinfo->mtime = s.st_mtime;
8970 compose->targetinfo->folder = draft;
8972 procmsg_msginfo_set_flags(compose->targetinfo, MSG_LOCKED, 0);
8973 compose->mode = COMPOSE_REEDIT;
8975 if (action == COMPOSE_AUTO_SAVE) {
8976 compose->autosaved_draft = compose->targetinfo;
8978 compose->modified = FALSE;
8979 compose_set_title(compose);
8983 g_mutex_unlock(compose->mutex);
8987 void compose_clear_exit_drafts(void)
8989 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
8990 DRAFTED_AT_EXIT, NULL);
8991 if (is_file_exist(filepath))
8992 claws_unlink(filepath);
8997 void compose_reopen_exit_drafts(void)
8999 gchar *filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
9000 DRAFTED_AT_EXIT, NULL);
9001 FILE *fp = fopen(filepath, "rb");
9005 while (fgets(buf, sizeof(buf), fp)) {
9006 gchar **parts = g_strsplit(buf, "\t", 2);
9007 const gchar *folder = parts[0];
9008 int msgnum = parts[1] ? atoi(parts[1]):-1;
9010 if (folder && *folder && msgnum > -1) {
9011 FolderItem *item = folder_find_item_from_identifier(folder);
9012 MsgInfo *info = folder_item_get_msginfo(item, msgnum);
9014 compose_reedit(info, FALSE);
9021 compose_clear_exit_drafts();
9024 static void compose_draft_cb(gpointer data, guint action, GtkWidget *widget)
9026 compose_draft(data, action);
9029 static void compose_attach_from_list(Compose *compose, GList *file_list, gboolean free_data)
9031 if (compose && file_list) {
9034 for ( tmp = file_list; tmp; tmp = tmp->next) {
9035 gchar *file = (gchar *) tmp->data;
9036 gchar *utf8_filename = conv_filename_to_utf8(file);
9037 compose_attach_append(compose, file, utf8_filename, NULL);
9038 compose_changed_cb(NULL, compose);
9043 g_free(utf8_filename);
9048 static void compose_attach_cb(gpointer data, guint action, GtkWidget *widget)
9050 Compose *compose = (Compose *)data;
9053 if (compose->redirect_filename != NULL)
9056 file_list = filesel_select_multiple_files_open(_("Select file"));
9059 compose_attach_from_list(compose, file_list, TRUE);
9060 g_list_free(file_list);
9064 static void compose_insert_file_cb(gpointer data, guint action,
9067 Compose *compose = (Compose *)data;
9070 file_list = filesel_select_multiple_files_open(_("Select file"));
9075 for ( tmp = file_list; tmp; tmp = tmp->next) {
9076 gchar *file = (gchar *) tmp->data;
9077 gchar *filedup = g_strdup(file);
9078 gchar *shortfile = g_path_get_basename(filedup);
9079 ComposeInsertResult res;
9081 res = compose_insert_file(compose, file);
9082 if (res == COMPOSE_INSERT_READ_ERROR) {
9083 alertpanel_error(_("File '%s' could not be read."), shortfile);
9084 } else if (res == COMPOSE_INSERT_INVALID_CHARACTER) {
9085 alertpanel_error(_("File '%s' contained invalid characters\n"
9086 "for the current encoding, insertion may be incorrect."), shortfile);
9092 g_list_free(file_list);
9096 static void compose_insert_sig_cb(gpointer data, guint action,
9099 Compose *compose = (Compose *)data;
9101 compose_insert_sig(compose, FALSE);
9104 static gint compose_delete_cb(GtkWidget *widget, GdkEventAny *event,
9108 Compose *compose = (Compose *)data;
9110 gtkut_widget_get_uposition(widget, &x, &y);
9111 prefs_common.compose_x = x;
9112 prefs_common.compose_y = y;
9114 if (compose->sending || compose->updating)
9116 compose_close_cb(compose, 0, NULL);
9120 void compose_close_toolbar(Compose *compose)
9122 compose_close_cb(compose, 0, NULL);
9125 static void compose_close_cb(gpointer data, guint action, GtkWidget *widget)
9127 Compose *compose = (Compose *)data;
9131 if (compose->exteditor_tag != -1) {
9132 if (!compose_ext_editor_kill(compose))
9137 if (compose->modified) {
9138 if (!g_mutex_trylock(compose->mutex)) {
9139 /* we don't want to lock the mutex once it's available,
9140 * because as the only other part of compose.c locking
9141 * it is compose_close - which means once unlocked,
9142 * the compose struct will be freed */
9143 debug_print("couldn't lock mutex, probably sending\n");
9146 val = alertpanel(_("Discard message"),
9147 _("This message has been modified. Discard it?"),
9148 _("_Discard"), _("_Save to Drafts"), GTK_STOCK_CANCEL);
9149 g_mutex_unlock(compose->mutex);
9151 case G_ALERTDEFAULT:
9152 if (prefs_common.autosave)
9153 compose_remove_draft(compose);
9155 case G_ALERTALTERNATE:
9156 compose_draft_cb(data, COMPOSE_QUIT_EDITING, NULL);
9163 compose_close(compose);
9166 static void compose_set_encoding_cb(gpointer data, guint action,
9169 Compose *compose = (Compose *)data;
9171 if (GTK_CHECK_MENU_ITEM(widget)->active)
9172 compose->out_encoding = (CharSet)action;
9175 static void compose_address_cb(gpointer data, guint action, GtkWidget *widget)
9177 Compose *compose = (Compose *)data;
9179 addressbook_open(compose);
9182 static void compose_template_activate_cb(GtkWidget *widget, gpointer data)
9184 Compose *compose = (Compose *)data;
9189 tmpl = g_object_get_data(G_OBJECT(widget), "template");
9190 g_return_if_fail(tmpl != NULL);
9192 msg = g_strdup_printf(_("Do you want to apply the template '%s' ?"),
9194 val = alertpanel(_("Apply template"), msg,
9195 _("_Replace"), _("_Insert"), GTK_STOCK_CANCEL);
9198 if (val == G_ALERTDEFAULT)
9199 compose_template_apply(compose, tmpl, TRUE);
9200 else if (val == G_ALERTALTERNATE)
9201 compose_template_apply(compose, tmpl, FALSE);
9204 static void compose_ext_editor_cb(gpointer data, guint action,
9207 Compose *compose = (Compose *)data;
9209 compose_exec_ext_editor(compose);
9212 static void compose_undo_cb(Compose *compose)
9214 gboolean prev_autowrap = compose->autowrap;
9216 compose->autowrap = FALSE;
9217 undo_undo(compose->undostruct);
9218 compose->autowrap = prev_autowrap;
9221 static void compose_redo_cb(Compose *compose)
9223 gboolean prev_autowrap = compose->autowrap;
9225 compose->autowrap = FALSE;
9226 undo_redo(compose->undostruct);
9227 compose->autowrap = prev_autowrap;
9230 static void entry_cut_clipboard(GtkWidget *entry)
9232 if (GTK_IS_EDITABLE(entry))
9233 gtk_editable_cut_clipboard (GTK_EDITABLE(entry));
9234 else if (GTK_IS_TEXT_VIEW(entry))
9235 gtk_text_buffer_cut_clipboard(
9236 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9237 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
9241 static void entry_copy_clipboard(GtkWidget *entry)
9243 if (GTK_IS_EDITABLE(entry))
9244 gtk_editable_copy_clipboard (GTK_EDITABLE(entry));
9245 else if (GTK_IS_TEXT_VIEW(entry))
9246 gtk_text_buffer_copy_clipboard(
9247 gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry)),
9248 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
9251 static void entry_paste_clipboard(Compose *compose, GtkWidget *entry,
9252 gboolean wrap, GdkAtom clip, GtkTextIter *insert_place)
9254 if (GTK_IS_TEXT_VIEW(entry)) {
9255 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9256 GtkTextMark *mark_start = gtk_text_buffer_get_insert(buffer);
9257 GtkTextIter start_iter, end_iter;
9259 gchar *contents = gtk_clipboard_wait_for_text(gtk_clipboard_get(clip));
9261 if (contents == NULL)
9264 undo_paste_clipboard(GTK_TEXT_VIEW(compose->text), compose->undostruct);
9266 /* we shouldn't delete the selection when middle-click-pasting, or we
9267 * can't mid-click-paste our own selection */
9268 if (clip != GDK_SELECTION_PRIMARY) {
9269 gtk_text_buffer_delete_selection(buffer, FALSE, TRUE);
9272 if (insert_place == NULL) {
9273 /* if insert_place isn't specified, insert at the cursor.
9274 * used for Ctrl-V pasting */
9275 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9276 start = gtk_text_iter_get_offset(&start_iter);
9277 gtk_text_buffer_insert(buffer, &start_iter, contents, strlen(contents));
9279 /* if insert_place is specified, paste here.
9280 * used for mid-click-pasting */
9281 start = gtk_text_iter_get_offset(insert_place);
9282 gtk_text_buffer_insert(buffer, insert_place, contents, strlen(contents));
9286 /* paste unwrapped: mark the paste so it's not wrapped later */
9287 end = start + strlen(contents);
9288 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
9289 gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
9290 gtk_text_buffer_apply_tag_by_name(buffer, "no_wrap", &start_iter, &end_iter);
9291 } else if (wrap && clip == GDK_SELECTION_PRIMARY) {
9292 /* rewrap paragraph now (after a mid-click-paste) */
9293 mark_start = gtk_text_buffer_get_insert(buffer);
9294 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark_start);
9295 gtk_text_iter_backward_char(&start_iter);
9296 compose_beautify_paragraph(compose, &start_iter, TRUE);
9298 } else if (GTK_IS_EDITABLE(entry))
9299 gtk_editable_paste_clipboard (GTK_EDITABLE(entry));
9301 compose->modified = TRUE;
9304 static void entry_allsel(GtkWidget *entry)
9306 if (GTK_IS_EDITABLE(entry))
9307 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
9308 else if (GTK_IS_TEXT_VIEW(entry)) {
9309 GtkTextIter startiter, enditer;
9310 GtkTextBuffer *textbuf;
9312 textbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(entry));
9313 gtk_text_buffer_get_start_iter(textbuf, &startiter);
9314 gtk_text_buffer_get_end_iter(textbuf, &enditer);
9316 gtk_text_buffer_move_mark_by_name(textbuf,
9317 "selection_bound", &startiter);
9318 gtk_text_buffer_move_mark_by_name(textbuf,
9319 "insert", &enditer);
9323 static void compose_cut_cb(Compose *compose)
9325 if (compose->focused_editable
9326 #ifndef GENERIC_UMPC
9327 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9330 entry_cut_clipboard(compose->focused_editable);
9333 static void compose_copy_cb(Compose *compose)
9335 if (compose->focused_editable
9336 #ifndef GENERIC_UMPC
9337 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9340 entry_copy_clipboard(compose->focused_editable);
9343 static void compose_paste_cb(Compose *compose)
9346 GtkTextBuffer *buffer;
9348 if (compose->focused_editable &&
9349 GTK_WIDGET_HAS_FOCUS(compose->focused_editable))
9350 entry_paste_clipboard(compose, compose->focused_editable,
9351 prefs_common.linewrap_pastes,
9352 GDK_SELECTION_CLIPBOARD, NULL);
9356 static void compose_paste_as_quote_cb(Compose *compose)
9358 gint wrap_quote = prefs_common.linewrap_quote;
9359 if (compose->focused_editable
9360 #ifndef GENERIC_UMPC
9361 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9364 /* let text_insert() (called directly or at a later time
9365 * after the gtk_editable_paste_clipboard) know that
9366 * text is to be inserted as a quotation. implemented
9367 * by using a simple refcount... */
9368 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data(
9369 G_OBJECT(compose->focused_editable),
9370 "paste_as_quotation"));
9371 g_object_set_data(G_OBJECT(compose->focused_editable),
9372 "paste_as_quotation",
9373 GINT_TO_POINTER(paste_as_quotation + 1));
9374 prefs_common.linewrap_quote = prefs_common.linewrap_pastes;
9375 entry_paste_clipboard(compose, compose->focused_editable,
9376 prefs_common.linewrap_pastes,
9377 GDK_SELECTION_CLIPBOARD, NULL);
9378 prefs_common.linewrap_quote = wrap_quote;
9382 static void compose_paste_no_wrap_cb(Compose *compose)
9385 GtkTextBuffer *buffer;
9387 if (compose->focused_editable
9388 #ifndef GENERIC_UMPC
9389 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9392 entry_paste_clipboard(compose, compose->focused_editable, FALSE,
9393 GDK_SELECTION_CLIPBOARD, NULL);
9397 static void compose_paste_wrap_cb(Compose *compose)
9400 GtkTextBuffer *buffer;
9402 if (compose->focused_editable
9403 #ifndef GENERIC_UMPC
9404 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9407 entry_paste_clipboard(compose, compose->focused_editable, TRUE,
9408 GDK_SELECTION_CLIPBOARD, NULL);
9412 static void compose_allsel_cb(Compose *compose)
9414 if (compose->focused_editable
9415 #ifndef GENERIC_UMPC
9416 && GTK_WIDGET_HAS_FOCUS(compose->focused_editable)
9419 entry_allsel(compose->focused_editable);
9422 static void textview_move_beginning_of_line (GtkTextView *text)
9424 GtkTextBuffer *buffer;
9428 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9430 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9431 mark = gtk_text_buffer_get_insert(buffer);
9432 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9433 gtk_text_iter_set_line_offset(&ins, 0);
9434 gtk_text_buffer_place_cursor(buffer, &ins);
9437 static void textview_move_forward_character (GtkTextView *text)
9439 GtkTextBuffer *buffer;
9443 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9445 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9446 mark = gtk_text_buffer_get_insert(buffer);
9447 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9448 if (gtk_text_iter_forward_cursor_position(&ins))
9449 gtk_text_buffer_place_cursor(buffer, &ins);
9452 static void textview_move_backward_character (GtkTextView *text)
9454 GtkTextBuffer *buffer;
9458 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9460 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9461 mark = gtk_text_buffer_get_insert(buffer);
9462 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9463 if (gtk_text_iter_backward_cursor_position(&ins))
9464 gtk_text_buffer_place_cursor(buffer, &ins);
9467 static void textview_move_forward_word (GtkTextView *text)
9469 GtkTextBuffer *buffer;
9474 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9476 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9477 mark = gtk_text_buffer_get_insert(buffer);
9478 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9479 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9480 if (gtk_text_iter_forward_word_ends(&ins, count)) {
9481 gtk_text_iter_backward_word_start(&ins);
9482 gtk_text_buffer_place_cursor(buffer, &ins);
9486 static void textview_move_backward_word (GtkTextView *text)
9488 GtkTextBuffer *buffer;
9493 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9495 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9496 mark = gtk_text_buffer_get_insert(buffer);
9497 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9498 count = gtk_text_iter_inside_word (&ins) ? 2 : 1;
9499 if (gtk_text_iter_backward_word_starts(&ins, 1))
9500 gtk_text_buffer_place_cursor(buffer, &ins);
9503 static void textview_move_end_of_line (GtkTextView *text)
9505 GtkTextBuffer *buffer;
9509 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9511 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9512 mark = gtk_text_buffer_get_insert(buffer);
9513 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9514 if (gtk_text_iter_forward_to_line_end(&ins))
9515 gtk_text_buffer_place_cursor(buffer, &ins);
9518 static void textview_move_next_line (GtkTextView *text)
9520 GtkTextBuffer *buffer;
9525 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9527 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9528 mark = gtk_text_buffer_get_insert(buffer);
9529 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9530 offset = gtk_text_iter_get_line_offset(&ins);
9531 if (gtk_text_iter_forward_line(&ins)) {
9532 gtk_text_iter_set_line_offset(&ins, offset);
9533 gtk_text_buffer_place_cursor(buffer, &ins);
9537 static void textview_move_previous_line (GtkTextView *text)
9539 GtkTextBuffer *buffer;
9544 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9546 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9547 mark = gtk_text_buffer_get_insert(buffer);
9548 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9549 offset = gtk_text_iter_get_line_offset(&ins);
9550 if (gtk_text_iter_backward_line(&ins)) {
9551 gtk_text_iter_set_line_offset(&ins, offset);
9552 gtk_text_buffer_place_cursor(buffer, &ins);
9556 static void textview_delete_forward_character (GtkTextView *text)
9558 GtkTextBuffer *buffer;
9560 GtkTextIter ins, end_iter;
9562 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9564 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9565 mark = gtk_text_buffer_get_insert(buffer);
9566 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9568 if (gtk_text_iter_forward_char(&end_iter)) {
9569 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9573 static void textview_delete_backward_character (GtkTextView *text)
9575 GtkTextBuffer *buffer;
9577 GtkTextIter ins, end_iter;
9579 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9581 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9582 mark = gtk_text_buffer_get_insert(buffer);
9583 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9585 if (gtk_text_iter_backward_char(&end_iter)) {
9586 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9590 static void textview_delete_forward_word (GtkTextView *text)
9592 GtkTextBuffer *buffer;
9594 GtkTextIter ins, end_iter;
9596 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9598 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9599 mark = gtk_text_buffer_get_insert(buffer);
9600 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9602 if (gtk_text_iter_forward_word_end(&end_iter)) {
9603 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9607 static void textview_delete_backward_word (GtkTextView *text)
9609 GtkTextBuffer *buffer;
9611 GtkTextIter ins, end_iter;
9613 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9615 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9616 mark = gtk_text_buffer_get_insert(buffer);
9617 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9619 if (gtk_text_iter_backward_word_start(&end_iter)) {
9620 gtk_text_buffer_delete(buffer, &end_iter, &ins);
9624 static void textview_delete_line (GtkTextView *text)
9626 GtkTextBuffer *buffer;
9628 GtkTextIter ins, start_iter, end_iter;
9630 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9632 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9633 mark = gtk_text_buffer_get_insert(buffer);
9634 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9637 gtk_text_iter_set_line_offset(&start_iter, 0);
9640 if (gtk_text_iter_ends_line(&end_iter)){
9641 if (!gtk_text_iter_forward_char(&end_iter))
9642 gtk_text_iter_backward_char(&start_iter);
9645 gtk_text_iter_forward_to_line_end(&end_iter);
9646 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
9649 static void textview_delete_to_line_end (GtkTextView *text)
9651 GtkTextBuffer *buffer;
9653 GtkTextIter ins, end_iter;
9655 g_return_if_fail(GTK_IS_TEXT_VIEW(text));
9657 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text));
9658 mark = gtk_text_buffer_get_insert(buffer);
9659 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
9661 if (gtk_text_iter_ends_line(&end_iter))
9662 gtk_text_iter_forward_char(&end_iter);
9664 gtk_text_iter_forward_to_line_end(&end_iter);
9665 gtk_text_buffer_delete(buffer, &ins, &end_iter);
9668 static void compose_advanced_action_cb(Compose *compose,
9669 ComposeCallAdvancedAction action)
9671 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
9673 void (*do_action) (GtkTextView *text);
9674 } action_table[] = {
9675 {textview_move_beginning_of_line},
9676 {textview_move_forward_character},
9677 {textview_move_backward_character},
9678 {textview_move_forward_word},
9679 {textview_move_backward_word},
9680 {textview_move_end_of_line},
9681 {textview_move_next_line},
9682 {textview_move_previous_line},
9683 {textview_delete_forward_character},
9684 {textview_delete_backward_character},
9685 {textview_delete_forward_word},
9686 {textview_delete_backward_word},
9687 {textview_delete_line},
9688 {textview_delete_to_line_end}
9691 if (!GTK_WIDGET_HAS_FOCUS(text)) return;
9693 if (action >= COMPOSE_CALL_ADVANCED_ACTION_MOVE_BEGINNING_OF_LINE &&
9694 action <= COMPOSE_CALL_ADVANCED_ACTION_DELETE_TO_LINE_END) {
9695 if (action_table[action].do_action)
9696 action_table[action].do_action(text);
9698 g_warning("Not implemented yet.");
9702 static void compose_grab_focus_cb(GtkWidget *widget, Compose *compose)
9706 if (GTK_IS_EDITABLE(widget)) {
9707 str = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
9708 gtk_editable_set_position(GTK_EDITABLE(widget),
9711 if (widget->parent && widget->parent->parent
9712 && widget->parent->parent->parent) {
9713 if (GTK_IS_SCROLLED_WINDOW(widget->parent->parent->parent)) {
9714 gint y = widget->allocation.y;
9715 gint height = widget->allocation.height;
9716 GtkAdjustment *shown = gtk_scrolled_window_get_vadjustment
9717 (GTK_SCROLLED_WINDOW(widget->parent->parent->parent));
9719 if (y < (int)shown->value) {
9720 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown), y - 1);
9722 if (y + height > (int)shown->value + (int)shown->page_size) {
9723 if (y - height - 1 < (int)shown->upper - (int)shown->page_size) {
9724 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9725 y + height - (int)shown->page_size - 1);
9727 gtk_adjustment_set_value(GTK_ADJUSTMENT(shown),
9728 (int)shown->upper - (int)shown->page_size - 1);
9735 if (GTK_IS_EDITABLE(widget) || GTK_IS_TEXT_VIEW(widget))
9736 compose->focused_editable = widget;
9739 if (GTK_IS_TEXT_VIEW(widget)
9740 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->edit_vbox) {
9741 gtk_widget_ref(compose->notebook);
9742 gtk_widget_ref(compose->edit_vbox);
9743 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9744 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9745 gtk_paned_add1(GTK_PANED(compose->paned), compose->edit_vbox);
9746 gtk_paned_add2(GTK_PANED(compose->paned), compose->notebook);
9747 gtk_widget_unref(compose->notebook);
9748 gtk_widget_unref(compose->edit_vbox);
9749 g_signal_handlers_block_by_func(G_OBJECT(widget),
9750 G_CALLBACK(compose_grab_focus_cb),
9752 gtk_widget_grab_focus(widget);
9753 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9754 G_CALLBACK(compose_grab_focus_cb),
9756 } else if (!GTK_IS_TEXT_VIEW(widget)
9757 && gtk_paned_get_child1(GTK_PANED(compose->paned)) != compose->notebook) {
9758 gtk_widget_ref(compose->notebook);
9759 gtk_widget_ref(compose->edit_vbox);
9760 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->notebook);
9761 gtk_container_remove(GTK_CONTAINER(compose->paned), compose->edit_vbox);
9762 gtk_paned_add1(GTK_PANED(compose->paned), compose->notebook);
9763 gtk_paned_add2(GTK_PANED(compose->paned), compose->edit_vbox);
9764 gtk_widget_unref(compose->notebook);
9765 gtk_widget_unref(compose->edit_vbox);
9766 g_signal_handlers_block_by_func(G_OBJECT(widget),
9767 G_CALLBACK(compose_grab_focus_cb),
9769 gtk_widget_grab_focus(widget);
9770 g_signal_handlers_unblock_by_func(G_OBJECT(widget),
9771 G_CALLBACK(compose_grab_focus_cb),
9777 static void compose_changed_cb(GtkTextBuffer *textbuf, Compose *compose)
9779 compose->modified = TRUE;
9780 #ifndef GENERIC_UMPC
9781 compose_set_title(compose);
9785 static void compose_wrap_cb(gpointer data, guint action, GtkWidget *widget)
9787 Compose *compose = (Compose *)data;
9790 compose_wrap_all_full(compose, TRUE);
9792 compose_beautify_paragraph(compose, NULL, TRUE);
9795 static void compose_find_cb(gpointer data, guint action, GtkWidget *widget)
9797 Compose *compose = (Compose *)data;
9799 message_search_compose(compose);
9802 static void compose_toggle_autowrap_cb(gpointer data, guint action,
9805 Compose *compose = (Compose *)data;
9806 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9807 if (compose->autowrap)
9808 compose_wrap_all_full(compose, TRUE);
9809 compose->autowrap = GTK_CHECK_MENU_ITEM(widget)->active;
9812 static void compose_toggle_sign_cb(gpointer data, guint action,
9815 Compose *compose = (Compose *)data;
9817 if (GTK_CHECK_MENU_ITEM(widget)->active)
9818 compose->use_signing = TRUE;
9820 compose->use_signing = FALSE;
9823 static void compose_toggle_encrypt_cb(gpointer data, guint action,
9826 Compose *compose = (Compose *)data;
9828 if (GTK_CHECK_MENU_ITEM(widget)->active)
9829 compose->use_encryption = TRUE;
9831 compose->use_encryption = FALSE;
9834 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn)
9836 g_free(compose->privacy_system);
9838 compose->privacy_system = g_strdup(account->default_privacy_system);
9839 compose_update_privacy_system_menu_item(compose, warn);
9842 static void compose_toggle_ruler_cb(gpointer data, guint action,
9845 Compose *compose = (Compose *)data;
9847 if (GTK_CHECK_MENU_ITEM(widget)->active) {
9848 gtk_widget_show(compose->ruler_hbox);
9849 prefs_common.show_ruler = TRUE;
9851 gtk_widget_hide(compose->ruler_hbox);
9852 gtk_widget_queue_resize(compose->edit_vbox);
9853 prefs_common.show_ruler = FALSE;
9857 static void compose_attach_drag_received_cb (GtkWidget *widget,
9858 GdkDragContext *context,
9861 GtkSelectionData *data,
9866 Compose *compose = (Compose *)user_data;
9869 if (gdk_atom_name(data->type) &&
9870 !strcmp(gdk_atom_name(data->type), "text/uri-list")
9871 && gtk_drag_get_source_widget(context) !=
9872 summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9873 list = uri_list_extract_filenames((const gchar *)data->data);
9874 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9875 gchar *utf8_filename = conv_filename_to_utf8((const gchar *)tmp->data);
9876 compose_attach_append
9877 (compose, (const gchar *)tmp->data,
9878 utf8_filename, NULL);
9879 g_free(utf8_filename);
9881 if (list) compose_changed_cb(NULL, compose);
9882 list_free_strings(list);
9884 } else if (gtk_drag_get_source_widget(context)
9885 == summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
9886 /* comes from our summaryview */
9887 SummaryView * summaryview = NULL;
9888 GSList * list = NULL, *cur = NULL;
9890 if (mainwindow_get_mainwindow())
9891 summaryview = mainwindow_get_mainwindow()->summaryview;
9894 list = summary_get_selected_msg_list(summaryview);
9896 for (cur = list; cur; cur = cur->next) {
9897 MsgInfo *msginfo = (MsgInfo *)cur->data;
9900 file = procmsg_get_message_file_full(msginfo,
9903 compose_attach_append(compose, (const gchar *)file,
9904 (const gchar *)file, "message/rfc822");
9912 static gboolean compose_drag_drop(GtkWidget *widget,
9913 GdkDragContext *drag_context,
9915 guint time, gpointer user_data)
9917 /* not handling this signal makes compose_insert_drag_received_cb
9922 static void compose_insert_drag_received_cb (GtkWidget *widget,
9923 GdkDragContext *drag_context,
9926 GtkSelectionData *data,
9931 Compose *compose = (Compose *)user_data;
9934 /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
9936 if (gdk_atom_name(data->type) && !strcmp(gdk_atom_name(data->type), "text/uri-list")) {
9937 AlertValue val = G_ALERTDEFAULT;
9939 list = uri_list_extract_filenames((const gchar *)data->data);
9941 if (list == NULL && strstr((gchar *)(data->data), "://")) {
9942 /* Assume a list of no files, and data has ://, is a remote link */
9943 gchar *tmpdata = g_strstrip(g_strdup((const gchar *)data->data));
9944 gchar *tmpfile = get_tmp_file();
9945 str_write_to_file(tmpdata, tmpfile);
9947 compose_insert_file(compose, tmpfile);
9948 claws_unlink(tmpfile);
9950 gtk_drag_finish(drag_context, TRUE, FALSE, time);
9951 compose_beautify_paragraph(compose, NULL, TRUE);
9954 switch (prefs_common.compose_dnd_mode) {
9955 case COMPOSE_DND_ASK:
9956 val = alertpanel_full(_("Insert or attach?"),
9957 _("Do you want to insert the contents of the file(s) "
9958 "into the message body, or attach it to the email?"),
9959 GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
9960 TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
9962 case COMPOSE_DND_INSERT:
9963 val = G_ALERTALTERNATE;
9965 case COMPOSE_DND_ATTACH:
9969 /* unexpected case */
9970 g_warning("error: unexpected compose_dnd_mode option value in compose_insert_drag_received_cb()");
9973 if (val & G_ALERTDISABLE) {
9974 val &= ~G_ALERTDISABLE;
9975 /* remember what action to perform by default, only if we don't click Cancel */
9976 if (val == G_ALERTALTERNATE)
9977 prefs_common.compose_dnd_mode = COMPOSE_DND_INSERT;
9978 else if (val == G_ALERTOTHER)
9979 prefs_common.compose_dnd_mode = COMPOSE_DND_ATTACH;
9982 if (val == G_ALERTDEFAULT || val == G_ALERTCANCEL) {
9983 gtk_drag_finish(drag_context, FALSE, FALSE, time);
9984 list_free_strings(list);
9987 } else if (val == G_ALERTOTHER) {
9988 compose_attach_drag_received_cb(widget, drag_context, x, y, data, info, time, user_data);
9989 list_free_strings(list);
9994 for (tmp = list; tmp != NULL; tmp = tmp->next) {
9995 compose_insert_file(compose, (const gchar *)tmp->data);
9997 list_free_strings(list);
9999 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10002 #if GTK_CHECK_VERSION(2, 8, 0)
10003 /* do nothing, handled by GTK */
10005 gchar *tmpfile = get_tmp_file();
10006 str_write_to_file((const gchar *)data->data, tmpfile);
10007 compose_insert_file(compose, tmpfile);
10008 claws_unlink(tmpfile);
10010 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10014 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10017 static void compose_header_drag_received_cb (GtkWidget *widget,
10018 GdkDragContext *drag_context,
10021 GtkSelectionData *data,
10024 gpointer user_data)
10026 GtkEditable *entry = (GtkEditable *)user_data;
10027 gchar *email = (gchar *)data->data;
10029 /* strangely, testing data->type == gdk_atom_intern("text/plain", TRUE)
10032 if (!strncmp(email, "mailto:", strlen("mailto:"))) {
10033 gchar *decoded=g_new(gchar, strlen(email));
10036 email += strlen("mailto:");
10037 decode_uri(decoded, email); /* will fit */
10038 gtk_editable_delete_text(entry, 0, -1);
10039 gtk_editable_insert_text(entry, decoded, strlen(decoded), &start);
10040 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10044 gtk_drag_finish(drag_context, TRUE, FALSE, time);
10047 static void compose_toggle_return_receipt_cb(gpointer data, guint action,
10050 Compose *compose = (Compose *)data;
10052 if (GTK_CHECK_MENU_ITEM(widget)->active)
10053 compose->return_receipt = TRUE;
10055 compose->return_receipt = FALSE;
10058 static void compose_toggle_remove_refs_cb(gpointer data, guint action,
10061 Compose *compose = (Compose *)data;
10063 if (GTK_CHECK_MENU_ITEM(widget)->active)
10064 compose->remove_references = TRUE;
10066 compose->remove_references = FALSE;
10069 static gboolean compose_headerentry_key_press_event_cb(GtkWidget *entry,
10070 GdkEventKey *event,
10071 ComposeHeaderEntry *headerentry)
10073 if ((g_slist_length(headerentry->compose->header_list) > 0) &&
10074 ((headerentry->headernum + 1) != headerentry->compose->header_nextrow) &&
10075 !(event->state & GDK_MODIFIER_MASK) &&
10076 (event->keyval == GDK_BackSpace) &&
10077 (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) == 0)) {
10078 gtk_container_remove
10079 (GTK_CONTAINER(headerentry->compose->header_table),
10080 headerentry->combo);
10081 gtk_container_remove
10082 (GTK_CONTAINER(headerentry->compose->header_table),
10083 headerentry->entry);
10084 headerentry->compose->header_list =
10085 g_slist_remove(headerentry->compose->header_list,
10087 g_free(headerentry);
10088 } else if (event->keyval == GDK_Tab) {
10089 if (headerentry->compose->header_last == headerentry) {
10090 /* Override default next focus, and give it to subject_entry
10091 * instead of notebook tabs
10093 g_signal_stop_emission_by_name(G_OBJECT(entry), "key-press-event");
10094 gtk_widget_grab_focus(headerentry->compose->subject_entry);
10101 static gboolean compose_headerentry_changed_cb(GtkWidget *entry,
10102 ComposeHeaderEntry *headerentry)
10104 if (strlen(gtk_entry_get_text(GTK_ENTRY(entry))) != 0) {
10105 compose_create_header_entry(headerentry->compose);
10106 g_signal_handlers_disconnect_matched
10107 (G_OBJECT(entry), G_SIGNAL_MATCH_DATA,
10108 0, 0, NULL, NULL, headerentry);
10110 /* Automatically scroll down */
10111 compose_show_first_last_header(headerentry->compose, FALSE);
10117 static void compose_show_first_last_header(Compose *compose, gboolean show_first)
10119 GtkAdjustment *vadj;
10121 g_return_if_fail(compose);
10122 g_return_if_fail(GTK_IS_WIDGET(compose->header_table));
10123 g_return_if_fail(GTK_IS_VIEWPORT(compose->header_table->parent));
10125 vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(compose->header_table->parent));
10126 gtk_adjustment_set_value(vadj, (show_first ? vadj->lower : vadj->upper));
10127 gtk_adjustment_changed(vadj);
10130 static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
10131 const gchar *text, gint len, Compose *compose)
10133 gint paste_as_quotation = GPOINTER_TO_INT(g_object_get_data
10134 (G_OBJECT(compose->text), "paste_as_quotation"));
10137 g_return_if_fail(text != NULL);
10139 g_signal_handlers_block_by_func(G_OBJECT(buffer),
10140 G_CALLBACK(text_inserted),
10142 if (paste_as_quotation) {
10144 const gchar *qmark;
10146 GtkTextIter start_iter;
10149 len = strlen(text);
10151 new_text = g_strndup(text, len);
10153 qmark = compose_quote_char_from_context(compose);
10155 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10156 gtk_text_buffer_place_cursor(buffer, iter);
10158 pos = gtk_text_iter_get_offset(iter);
10160 compose_quote_fmt(compose, NULL, "%Q", qmark, new_text, TRUE, FALSE,
10161 _("Quote format error at line %d."));
10162 quote_fmt_reset_vartable();
10164 g_object_set_data(G_OBJECT(compose->text), "paste_as_quotation",
10165 GINT_TO_POINTER(paste_as_quotation - 1));
10167 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10168 gtk_text_buffer_place_cursor(buffer, iter);
10169 gtk_text_buffer_delete_mark(buffer, mark);
10171 gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, pos);
10172 mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter, FALSE);
10173 compose_beautify_paragraph(compose, &start_iter, FALSE);
10174 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
10175 gtk_text_buffer_delete_mark(buffer, mark);
10177 if (strcmp(text, "\n") || compose->automatic_break
10178 || gtk_text_iter_starts_line(iter))
10179 gtk_text_buffer_insert(buffer, iter, text, len);
10181 /* check if the preceding is just whitespace or quote */
10182 GtkTextIter start_line;
10183 gchar *tmp = NULL, *quote = NULL;
10184 gint quote_len = 0, is_normal = 0;
10185 start_line = *iter;
10186 gtk_text_iter_set_line_offset(&start_line, 0);
10187 tmp = gtk_text_buffer_get_text(buffer, &start_line, iter, FALSE);
10189 if (*tmp == '\0') {
10192 quote = compose_get_quote_str(buffer, &start_line, "e_len);
10200 gtk_text_buffer_insert(buffer, iter, text, len);
10202 gtk_text_buffer_insert_with_tags_by_name(buffer,
10203 iter, text, len, "no_join", NULL);
10208 if (!paste_as_quotation) {
10209 mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE);
10210 compose_beautify_paragraph(compose, iter, FALSE);
10211 gtk_text_buffer_get_iter_at_mark(buffer, iter, mark);
10212 gtk_text_buffer_delete_mark(buffer, mark);
10215 g_signal_handlers_unblock_by_func(G_OBJECT(buffer),
10216 G_CALLBACK(text_inserted),
10218 g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
10220 if (prefs_common.autosave &&
10221 gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
10222 compose->draft_timeout_tag != -2 /* disabled while loading */)
10223 compose->draft_timeout_tag = g_timeout_add
10224 (500, (GtkFunction) compose_defer_auto_save_draft, compose);
10226 static gint compose_defer_auto_save_draft(Compose *compose)
10228 compose->draft_timeout_tag = -1;
10229 compose_draft_cb((gpointer)compose, COMPOSE_AUTO_SAVE, NULL);
10234 static void compose_check_all(Compose *compose)
10236 if (compose->gtkaspell)
10237 gtkaspell_check_all(compose->gtkaspell);
10240 static void compose_highlight_all(Compose *compose)
10242 if (compose->gtkaspell)
10243 gtkaspell_highlight_all(compose->gtkaspell);
10246 static void compose_check_backwards(Compose *compose)
10248 if (compose->gtkaspell)
10249 gtkaspell_check_backwards(compose->gtkaspell);
10251 GtkItemFactory *ifactory;
10252 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10253 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10254 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10258 static void compose_check_forwards_go(Compose *compose)
10260 if (compose->gtkaspell)
10261 gtkaspell_check_forwards_go(compose->gtkaspell);
10263 GtkItemFactory *ifactory;
10264 ifactory = gtk_item_factory_from_widget(compose->popupmenu);
10265 menu_set_sensitive(ifactory, "/Edit/Check backwards misspelled word", FALSE);
10266 menu_set_sensitive(ifactory, "/Edit/Forward to next misspelled word", FALSE);
10272 *\brief Guess originating forward account from MsgInfo and several
10273 * "common preference" settings. Return NULL if no guess.
10275 static PrefsAccount *compose_guess_forward_account_from_msginfo(MsgInfo *msginfo)
10277 PrefsAccount *account = NULL;
10279 g_return_val_if_fail(msginfo, NULL);
10280 g_return_val_if_fail(msginfo->folder, NULL);
10281 g_return_val_if_fail(msginfo->folder->prefs, NULL);
10283 if (msginfo->folder->prefs->enable_default_account)
10284 account = account_find_from_id(msginfo->folder->prefs->default_account);
10287 account = msginfo->folder->folder->account;
10289 if (!account && msginfo->to && prefs_common.forward_account_autosel) {
10291 Xstrdup_a(to, msginfo->to, return NULL);
10292 extract_address(to);
10293 account = account_find_from_address(to, FALSE);
10296 if (!account && prefs_common.forward_account_autosel) {
10297 gchar cc[BUFFSIZE];
10298 if (!procheader_get_header_from_msginfo
10299 (msginfo, cc,sizeof cc , "Cc:")) {
10300 gchar *buf = cc + strlen("Cc:");
10301 extract_address(buf);
10302 account = account_find_from_address(buf, FALSE);
10306 if (!account && prefs_common.forward_account_autosel) {
10307 gchar deliveredto[BUFFSIZE];
10308 if (!procheader_get_header_from_msginfo
10309 (msginfo, deliveredto,sizeof deliveredto , "Delivered-To:")) {
10310 gchar *buf = deliveredto + strlen("Delivered-To:");
10311 extract_address(buf);
10312 account = account_find_from_address(buf, FALSE);
10319 gboolean compose_close(Compose *compose)
10323 if (!g_mutex_trylock(compose->mutex)) {
10324 /* we have to wait for the (possibly deferred by auto-save)
10325 * drafting to be done, before destroying the compose under
10327 debug_print("waiting for drafting to finish...\n");
10328 compose_allow_user_actions(compose, FALSE);
10329 g_timeout_add (500, (GSourceFunc) compose_close, compose);
10332 g_return_val_if_fail(compose, FALSE);
10333 gtkut_widget_get_uposition(compose->window, &x, &y);
10334 prefs_common.compose_x = x;
10335 prefs_common.compose_y = y;
10336 g_mutex_unlock(compose->mutex);
10337 compose_destroy(compose);
10342 * Add entry field for each address in list.
10343 * \param compose E-Mail composition object.
10344 * \param listAddress List of (formatted) E-Mail addresses.
10346 static void compose_add_field_list( Compose *compose, GList *listAddress ) {
10349 node = listAddress;
10351 addr = ( gchar * ) node->data;
10352 compose_entry_append( compose, addr, COMPOSE_TO );
10353 node = g_list_next( node );
10357 static void compose_reply_from_messageview_real(MessageView *msgview, GSList *msginfo_list,
10358 guint action, gboolean opening_multiple)
10360 gchar *body = NULL;
10361 GSList *new_msglist = NULL;
10362 MsgInfo *tmp_msginfo = NULL;
10363 gboolean originally_enc = FALSE;
10364 Compose *compose = NULL;
10366 g_return_if_fail(msgview != NULL);
10368 g_return_if_fail(msginfo_list != NULL);
10370 if (g_slist_length(msginfo_list) == 1 && !opening_multiple) {
10371 MimeInfo *mimeinfo = messageview_get_selected_mime_part(msgview);
10372 MsgInfo *orig_msginfo = (MsgInfo *)msginfo_list->data;
10374 if (mimeinfo != NULL && mimeinfo->type == MIMETYPE_MESSAGE &&
10375 !g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
10376 tmp_msginfo = procmsg_msginfo_new_from_mimeinfo(
10377 orig_msginfo, mimeinfo);
10378 if (tmp_msginfo != NULL) {
10379 new_msglist = g_slist_append(NULL, tmp_msginfo);
10381 originally_enc = MSG_IS_ENCRYPTED(orig_msginfo->flags);
10382 tmp_msginfo->folder = orig_msginfo->folder;
10383 tmp_msginfo->msgnum = orig_msginfo->msgnum;
10384 if (orig_msginfo->tags)
10385 tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
10390 if (!opening_multiple)
10391 body = messageview_get_selection(msgview);
10394 compose = compose_reply_mode((ComposeMode)action, new_msglist, body);
10395 procmsg_msginfo_free(tmp_msginfo);
10396 g_slist_free(new_msglist);
10398 compose = compose_reply_mode((ComposeMode)action, msginfo_list, body);
10400 if (compose && originally_enc) {
10401 compose_force_encryption(compose, compose->account, FALSE);
10407 void compose_reply_from_messageview(MessageView *msgview, GSList *msginfo_list,
10410 if ((!prefs_common.forward_as_attachment || action != COMPOSE_FORWARD)
10411 && action != COMPOSE_FORWARD_AS_ATTACH && g_slist_length(msginfo_list) > 1) {
10412 GSList *cur = msginfo_list;
10413 gchar *msg = g_strdup_printf(_("You are about to reply to %d "
10414 "messages. Opening the windows "
10415 "could take some time. Do you "
10416 "want to continue?"),
10417 g_slist_length(msginfo_list));
10418 if (g_slist_length(msginfo_list) > 9
10419 && alertpanel(_("Warning"), msg, GTK_STOCK_CANCEL, "+" GTK_STOCK_YES, NULL)
10420 != G_ALERTALTERNATE) {
10425 /* We'll open multiple compose windows */
10426 /* let the WM place the next windows */
10427 compose_force_window_origin = FALSE;
10428 for (; cur; cur = cur->next) {
10430 tmplist.data = cur->data;
10431 tmplist.next = NULL;
10432 compose_reply_from_messageview_real(msgview, &tmplist, action, TRUE);
10434 compose_force_window_origin = TRUE;
10436 /* forwarding multiple mails as attachments is done via a
10437 * single compose window */
10438 compose_reply_from_messageview_real(msgview, msginfo_list, action, FALSE);
10442 void compose_set_position(Compose *compose, gint pos)
10444 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10446 gtkut_text_view_set_position(text, pos);
10449 gboolean compose_search_string(Compose *compose,
10450 const gchar *str, gboolean case_sens)
10452 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10454 return gtkut_text_view_search_string(text, str, case_sens);
10457 gboolean compose_search_string_backward(Compose *compose,
10458 const gchar *str, gboolean case_sens)
10460 GtkTextView *text = GTK_TEXT_VIEW(compose->text);
10462 return gtkut_text_view_search_string_backward(text, str, case_sens);
10465 /* allocate a msginfo structure and populate its data from a compose data structure */
10466 static MsgInfo *compose_msginfo_new_from_compose(Compose *compose)
10468 MsgInfo *newmsginfo;
10470 gchar buf[BUFFSIZE];
10472 g_return_val_if_fail( compose != NULL, NULL );
10474 newmsginfo = procmsg_msginfo_new();
10477 get_rfc822_date(buf, sizeof(buf));
10478 newmsginfo->date = g_strdup(buf);
10481 if (compose->from_name) {
10482 newmsginfo->from = gtk_editable_get_chars(GTK_EDITABLE(compose->from_name), 0, -1);
10483 newmsginfo->fromname = procheader_get_fromname(newmsginfo->from);
10487 if (compose->subject_entry)
10488 newmsginfo->subject = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
10490 /* to, cc, reply-to, newsgroups */
10491 for (list = compose->header_list; list; list = list->next) {
10492 gchar *header = gtk_editable_get_chars(
10494 GTK_BIN(((ComposeHeaderEntry *)list->data)->combo)->child), 0, -1);
10495 gchar *entry = gtk_editable_get_chars(
10496 GTK_EDITABLE(((ComposeHeaderEntry *)list->data)->entry), 0, -1);
10498 if ( strcasecmp(header, prefs_common_translated_header_name("To:")) == 0 ) {
10499 if ( newmsginfo->to == NULL ) {
10500 newmsginfo->to = g_strdup(entry);
10501 } else if (entry && *entry) {
10502 gchar *tmp = g_strconcat(newmsginfo->to, ", ", entry, NULL);
10503 g_free(newmsginfo->to);
10504 newmsginfo->to = tmp;
10507 if ( strcasecmp(header, prefs_common_translated_header_name("Cc:")) == 0 ) {
10508 if ( newmsginfo->cc == NULL ) {
10509 newmsginfo->cc = g_strdup(entry);
10510 } else if (entry && *entry) {
10511 gchar *tmp = g_strconcat(newmsginfo->cc, ", ", entry, NULL);
10512 g_free(newmsginfo->cc);
10513 newmsginfo->cc = tmp;
10516 if ( strcasecmp(header,
10517 prefs_common_translated_header_name("Newsgroups:")) == 0 ) {
10518 if ( newmsginfo->newsgroups == NULL ) {
10519 newmsginfo->newsgroups = g_strdup(entry);
10520 } else if (entry && *entry) {
10521 gchar *tmp = g_strconcat(newmsginfo->newsgroups, ", ", entry, NULL);
10522 g_free(newmsginfo->newsgroups);
10523 newmsginfo->newsgroups = tmp;
10531 /* other data is unset */
10537 /* update compose's dictionaries from folder dict settings */
10538 static void compose_set_dictionaries_from_folder_prefs(Compose *compose,
10539 FolderItem *folder_item)
10541 g_return_if_fail(compose != NULL);
10543 if (compose->gtkaspell && folder_item && folder_item->prefs) {
10544 FolderItemPrefs *prefs = folder_item->prefs;
10546 if (prefs->enable_default_dictionary)
10547 gtkaspell_change_dict(compose->gtkaspell,
10548 prefs->default_dictionary, FALSE);
10549 if (folder_item->prefs->enable_default_alt_dictionary)
10550 gtkaspell_change_alt_dict(compose->gtkaspell,
10551 prefs->default_alt_dictionary);
10552 if (prefs->enable_default_dictionary
10553 || prefs->enable_default_alt_dictionary)
10554 compose_spell_menu_changed(compose);